From d69858d3b32193139c7838d456a8be79048d61b6 Mon Sep 17 00:00:00 2001 From: Mike Hostetler <84222+mikehostetler@users.noreply.github.com> Date: Fri, 27 Feb 2026 13:10:46 -0600 Subject: [PATCH 1/2] feat(guardrails): harden workflow enforcement and extension path --- .dialyzer_ignore.exs | 2 + .github/workflows/ci.yml | 8 ++ AGENTS.md | 1 + CONTRIBUTING.md | 5 + GUARDRAILS.md | 48 +++++++++ lib/jido_shell/guardrails.ex | 101 +++++++++++------- lib/jido_shell/guardrails/rule.ex | 9 ++ .../guardrails/rules/collapsed_namespace.ex | 34 ++++++ .../guardrails/rules/legacy_layout.ex | 18 ++++ lib/mix/tasks/jido_shell.guardrails.ex | 12 ++- mix.exs | 3 +- test/jido/shell/guardrails_extension_test.exs | 51 +++++++++ test/mix/tasks/jido_shell.guardrails_test.exs | 44 +++++++- 13 files changed, 289 insertions(+), 47 deletions(-) create mode 100644 GUARDRAILS.md create mode 100644 lib/jido_shell/guardrails/rule.ex create mode 100644 lib/jido_shell/guardrails/rules/collapsed_namespace.ex create mode 100644 lib/jido_shell/guardrails/rules/legacy_layout.ex create mode 100644 test/jido/shell/guardrails_extension_test.exs diff --git a/.dialyzer_ignore.exs b/.dialyzer_ignore.exs index 8a5cfd5..a8c0620 100644 --- a/.dialyzer_ignore.exs +++ b/.dialyzer_ignore.exs @@ -2,6 +2,8 @@ # Mix.Task behaviour info not available in dialyzer context {"lib/mix/tasks/jido_shell.ex", :callback_info_missing}, {"lib/mix/tasks/jido_shell.ex", :unknown_function}, + {"lib/mix/tasks/jido_shell.guardrails.ex", :callback_info_missing}, + {"lib/mix/tasks/jido_shell.guardrails.ex", :unknown_function}, {"lib/mix/tasks/jido_shell.install.ex", :callback_info_missing}, {"lib/mix/tasks/jido_shell.install.ex", :unknown_function} ] diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 585c9eb..4b4850a 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -11,6 +11,14 @@ concurrency: cancel-in-progress: true jobs: + guardrails: + name: Guardrails + uses: agentjido/github-actions/.github/workflows/elixir-test.yml@main + with: + otp_versions: '["27"]' + elixir_versions: '["1.18"]' + test_command: mix jido_shell.guardrails + lint: name: Lint uses: agentjido/github-actions/.github/workflows/elixir-lint.yml@main diff --git a/AGENTS.md b/AGENTS.md index 6f8f896..f669a6a 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -12,6 +12,7 @@ mix compile --warnings-as-errors mix test mix test --include flaky mix coveralls +mix jido_shell.guardrails mix quality mix docs mix jido_shell diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 3c84faf..df3747a 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -16,12 +16,16 @@ mix test Run before opening a PR: ```bash +mix jido_shell.guardrails mix quality mix test mix test --include flaky mix coveralls ``` +`mix quality` runs `mix jido_shell.guardrails`, so namespace/layout regressions fail early in scripted workflows. +See [GUARDRAILS.md](GUARDRAILS.md) for extension guidance. + ## Common Commands ```bash @@ -38,6 +42,7 @@ mix docs 2. Add tests for behavior changes. 3. Keep docs and examples in sync. 4. Use conventional commits. +5. If namespace/layout conventions change, update guardrails and guardrail tests in the same PR. Examples: diff --git a/GUARDRAILS.md b/GUARDRAILS.md new file mode 100644 index 0000000..3b9c634 --- /dev/null +++ b/GUARDRAILS.md @@ -0,0 +1,48 @@ +# Guardrails Guide + +`jido_shell` guardrails protect namespace and file-layout conventions so regressions fail early in development and CI. + +Run guardrails directly: + +```bash +mix jido_shell.guardrails +``` + +`mix quality` runs this task automatically. + +## Current Conventions + +Guardrails currently enforce: + +- no collapsed namespace modules (for example `JidoShell.Foo`) +- no legacy `lib/jido/shell` layout paths + +## Extending Guardrails + +When conventions evolve, update guardrails and tests in the same PR. + +1. Add a rule module implementing `Jido.Shell.Guardrails.Rule`. +2. Return `:ok` or a list of `Jido.Shell.Guardrails.violation()` tuples from `check/1`. +3. Register the rule by either: +4. Adding it to `Jido.Shell.Guardrails.default_rules/0`. +5. Configuring it via `config :jido_shell, :guardrail_rules, [MyRule]`. +6. Add tests showing both pass and fail behavior for the new convention. + +Example: + +```elixir +defmodule Jido.Shell.Guardrails.Rules.MyConvention do + @behaviour Jido.Shell.Guardrails.Rule + + @impl true + def check(project_root) do + path = Path.join(project_root, "some/required/path") + + if File.exists?(path) do + :ok + else + [{:legacy_layout_path, %{path: "some/required/path"}}] + end + end +end +``` diff --git a/lib/jido_shell/guardrails.ex b/lib/jido_shell/guardrails.ex index 973bdba..fa9d551 100644 --- a/lib/jido_shell/guardrails.ex +++ b/lib/jido_shell/guardrails.ex @@ -1,19 +1,46 @@ defmodule Jido.Shell.Guardrails do - @moduledoc false + @moduledoc """ + Guardrails that enforce `jido_shell` namespace and layout conventions. - @collapsed_namespace_regex ~r/^\s*defmodule\s+(Jido[A-Z][\w\.]*)/m + Extension rules can be configured with: + + config :jido_shell, :guardrail_rules, [ + MyApp.CustomGuardrailRule + ] + """ @type violation :: {:collapsed_namespace_module, %{path: String.t(), line: pos_integer(), module: String.t()}} | {:legacy_layout_path, %{path: String.t()}} - @spec check(String.t()) :: :ok | {:error, [violation()]} - def check(project_root \\ File.cwd!()) when is_binary(project_root) do - violations = collapsed_namespace_violations(project_root) ++ legacy_layout_violations(project_root) + @default_rules [ + Jido.Shell.Guardrails.Rules.CollapsedNamespace, + Jido.Shell.Guardrails.Rules.LegacyLayout + ] + + @type option :: {:rules, [module()]} + @type options :: [option()] + + @spec check(String.t(), options()) :: :ok | {:error, [violation()]} + def check(project_root \\ File.cwd!(), opts \\ []) when is_binary(project_root) and is_list(opts) do + violations = + opts + |> rules() + |> Enum.flat_map(&run_rule(&1, project_root)) if violations == [], do: :ok, else: {:error, violations} end + @spec default_rules() :: [module()] + def default_rules, do: @default_rules + + @spec configured_rules() :: [module()] + def configured_rules do + @default_rules + |> Kernel.++(configured_extension_rules()) + |> Enum.uniq() + end + @spec format_violations([violation()]) :: String.t() def format_violations(violations) when is_list(violations) do header = @@ -29,48 +56,40 @@ defmodule Jido.Shell.Guardrails do |> Enum.join("\n") end - defp collapsed_namespace_violations(project_root) do - lib_paths = Path.wildcard(Path.join([project_root, "lib", "**", "*.ex"])) - - Enum.flat_map(lib_paths, fn path -> - path - |> File.read!() - |> String.split("\n") - |> Enum.with_index(1) - |> Enum.flat_map(fn {line, index} -> - case Regex.run(@collapsed_namespace_regex, line) do - [_, module] -> - [ - {:collapsed_namespace_module, - %{ - path: relative_path(project_root, path), - line: index, - module: module - }} - ] - - _ -> - [] - end - end) - end) + defp rules(opts) do + opts + |> Keyword.get(:rules, configured_rules()) + |> normalize_rules!() end - defp legacy_layout_violations(project_root) do - patterns = [ - Path.join([project_root, "lib", "jido", "shell.ex"]), - Path.join([project_root, "lib", "jido", "shell", "**", "*.ex"]) - ] + defp configured_extension_rules do + :jido_shell + |> Application.get_env(:guardrail_rules, []) + |> List.wrap() + |> normalize_rules!() + end - patterns - |> Enum.flat_map(&Path.wildcard/1) - |> Enum.map(fn path -> - {:legacy_layout_path, %{path: relative_path(project_root, path)}} + defp normalize_rules!(rules) when is_list(rules) do + Enum.map(rules, fn rule -> + if is_atom(rule) do + rule + else + raise ArgumentError, "guardrail rule #{inspect(rule)} must be a module atom" + end end) end - defp relative_path(project_root, path) do - Path.relative_to(path, project_root) + defp run_rule(rule, project_root) do + Code.ensure_loaded(rule) + + unless function_exported?(rule, :check, 1) do + raise ArgumentError, "guardrail rule #{inspect(rule)} must define check/1" + end + + case rule.check(project_root) do + :ok -> [] + violations when is_list(violations) -> violations + end end defp format_violation({:collapsed_namespace_module, %{path: path, line: line, module: module}}) do diff --git a/lib/jido_shell/guardrails/rule.ex b/lib/jido_shell/guardrails/rule.ex new file mode 100644 index 0000000..d02cae5 --- /dev/null +++ b/lib/jido_shell/guardrails/rule.ex @@ -0,0 +1,9 @@ +defmodule Jido.Shell.Guardrails.Rule do + @moduledoc """ + Behaviour for guardrail rules. + """ + + alias Jido.Shell.Guardrails + + @callback check(project_root :: String.t()) :: :ok | [Guardrails.violation()] +end diff --git a/lib/jido_shell/guardrails/rules/collapsed_namespace.ex b/lib/jido_shell/guardrails/rules/collapsed_namespace.ex new file mode 100644 index 0000000..6096be9 --- /dev/null +++ b/lib/jido_shell/guardrails/rules/collapsed_namespace.ex @@ -0,0 +1,34 @@ +defmodule Jido.Shell.Guardrails.Rules.CollapsedNamespace do + @moduledoc false + @behaviour Jido.Shell.Guardrails.Rule + + @collapsed_namespace_regex ~r/^\s*defmodule\s+(Jido[A-Z][\w\.]*)/m + + @impl true + def check(project_root) do + lib_paths = Path.wildcard(Path.join([project_root, "lib", "**", "*.ex"])) + + Enum.flat_map(lib_paths, fn path -> + path + |> File.read!() + |> String.split("\n") + |> Enum.with_index(1) + |> Enum.flat_map(fn {line, index} -> + case Regex.run(@collapsed_namespace_regex, line) do + [_, module] -> + [ + {:collapsed_namespace_module, + %{ + path: Path.relative_to(path, project_root), + line: index, + module: module + }} + ] + + _ -> + [] + end + end) + end) + end +end diff --git a/lib/jido_shell/guardrails/rules/legacy_layout.ex b/lib/jido_shell/guardrails/rules/legacy_layout.ex new file mode 100644 index 0000000..0e6afa1 --- /dev/null +++ b/lib/jido_shell/guardrails/rules/legacy_layout.ex @@ -0,0 +1,18 @@ +defmodule Jido.Shell.Guardrails.Rules.LegacyLayout do + @moduledoc false + @behaviour Jido.Shell.Guardrails.Rule + + @impl true + def check(project_root) do + patterns = [ + Path.join([project_root, "lib", "jido", "shell.ex"]), + Path.join([project_root, "lib", "jido", "shell", "**", "*.ex"]) + ] + + patterns + |> Enum.flat_map(&Path.wildcard/1) + |> Enum.map(fn path -> + {:legacy_layout_path, %{path: Path.relative_to(path, project_root)}} + end) + end +end diff --git a/lib/mix/tasks/jido_shell.guardrails.ex b/lib/mix/tasks/jido_shell.guardrails.ex index 9ad192e..2e3f8c4 100644 --- a/lib/mix/tasks/jido_shell.guardrails.ex +++ b/lib/mix/tasks/jido_shell.guardrails.ex @@ -8,9 +8,17 @@ defmodule Mix.Tasks.JidoShell.Guardrails do @shortdoc "Validate Jido.Shell namespace/layout guardrails" @impl Mix.Task - def run(_args) do - case Jido.Shell.Guardrails.check(File.cwd!()) do + def run(args) do + {opts, _argv, _invalid} = + OptionParser.parse(args, + strict: [root: :string] + ) + + root = Keyword.get(opts, :root, File.cwd!()) + + case Jido.Shell.Guardrails.check(root) do :ok -> + Mix.shell().info("jido_shell guardrails: ok") :ok {:error, violations} -> diff --git a/mix.exs b/mix.exs index 8ebf01e..491573e 100644 --- a/mix.exs +++ b/mix.exs @@ -108,7 +108,7 @@ defmodule Jido.Shell.MixProject do defp package do [ files: - ~w(lib mix.exs LICENSE README.md MIGRATION.md CHANGELOG.md CONTRIBUTING.md AGENTS.md usage-rules.md .formatter.exs), + ~w(lib mix.exs LICENSE README.md MIGRATION.md CHANGELOG.md CONTRIBUTING.md GUARDRAILS.md AGENTS.md usage-rules.md .formatter.exs), maintainers: ["Mike Hostetler"], licenses: ["Apache-2.0"], links: %{ @@ -130,6 +130,7 @@ defmodule Jido.Shell.MixProject do "MIGRATION.md", "CHANGELOG.md", "CONTRIBUTING.md", + "GUARDRAILS.md", "LICENSE" ], groups_for_modules: [ diff --git a/test/jido/shell/guardrails_extension_test.exs b/test/jido/shell/guardrails_extension_test.exs new file mode 100644 index 0000000..d1793e9 --- /dev/null +++ b/test/jido/shell/guardrails_extension_test.exs @@ -0,0 +1,51 @@ +defmodule Jido.Shell.GuardrailsExtensionTest do + use ExUnit.Case, async: false + + alias Jido.Shell.Guardrails + alias Jido.Shell.Guardrails.Rules.LegacyLayout + + defmodule CustomConventionRule do + @behaviour Jido.Shell.Guardrails.Rule + + @impl true + def check(_project_root) do + [{:legacy_layout_path, %{path: "custom/rule/path"}}] + end + end + + setup do + previous_rules = Application.get_env(:jido_shell, :guardrail_rules) + + on_exit(fn -> + if is_nil(previous_rules) do + Application.delete_env(:jido_shell, :guardrail_rules) + else + Application.put_env(:jido_shell, :guardrail_rules, previous_rules) + end + end) + + :ok + end + + test "configured extension rules are appended to default rules" do + Application.put_env(:jido_shell, :guardrail_rules, [CustomConventionRule]) + + assert {:error, violations} = Guardrails.check(File.cwd!()) + + assert {:legacy_layout_path, %{path: "custom/rule/path"}} in violations + end + + test "explicit rules option bypasses configured extension rules for targeted checks" do + Application.put_env(:jido_shell, :guardrail_rules, [CustomConventionRule]) + + assert :ok = Guardrails.check(File.cwd!(), rules: [LegacyLayout]) + end + + test "invalid configured extension rules raise a clear error" do + Application.put_env(:jido_shell, :guardrail_rules, ["not-a-module"]) + + assert_raise ArgumentError, ~r/must be a module atom/, fn -> + Guardrails.check(File.cwd!()) + end + end +end diff --git a/test/mix/tasks/jido_shell.guardrails_test.exs b/test/mix/tasks/jido_shell.guardrails_test.exs index a09d166..03dc131 100644 --- a/test/mix/tasks/jido_shell.guardrails_test.exs +++ b/test/mix/tasks/jido_shell.guardrails_test.exs @@ -1,8 +1,46 @@ defmodule Mix.Tasks.JidoShell.GuardrailsTest do - use ExUnit.Case, async: true + use ExUnit.Case, async: false + import ExUnit.CaptureIO - test "passes for the current project state" do + setup do Mix.Task.reenable("jido_shell.guardrails") - assert :ok = Mix.Tasks.JidoShell.Guardrails.run([]) + :ok + end + + test "passes for the current project state" do + output = + capture_io(fn -> + assert :ok = Mix.Tasks.JidoShell.Guardrails.run(["--root", File.cwd!()]) + end) + + assert output =~ "jido_shell guardrails: ok" + end + + test "raises for violated guardrails when checking a custom root" do + root = new_temp_project_root("mix_guardrails_failure") + on_exit(fn -> File.rm_rf(root) end) + + write_file(root, "lib/jido/shell/legacy.ex", "defmodule Jido.Shell.Legacy do\nend\n") + + assert_raise Mix.Error, ~r/Jido.Shell guardrails failed/, fn -> + Mix.Tasks.JidoShell.Guardrails.run(["--root", root]) + end + end + + defp new_temp_project_root(prefix) do + path = + Path.join( + System.tmp_dir!(), + "jido_shell_guardrails_#{prefix}_#{System.unique_integer([:positive, :monotonic])}" + ) + + File.mkdir_p!(path) + path + end + + defp write_file(root, relative_path, body) do + absolute_path = Path.join(root, relative_path) + File.mkdir_p!(Path.dirname(absolute_path)) + File.write!(absolute_path, body) end end From 446c8fda42382876f706cef08cbc62fcffd3f149 Mon Sep 17 00:00:00 2001 From: Mike Hostetler <84222+mikehostetler@users.noreply.github.com> Date: Fri, 27 Feb 2026 13:16:12 -0600 Subject: [PATCH 2/2] chore(deps): remove unused lockfile entries --- mix.lock | 7 ------- 1 file changed, 7 deletions(-) diff --git a/mix.lock b/mix.lock index 4009c34..a8165f5 100644 --- a/mix.lock +++ b/mix.lock @@ -3,10 +3,7 @@ "certifi": {:hex, :certifi, "2.15.0", "0e6e882fcdaaa0a5a9f2b3db55b1394dba07e8d6d9bcad08318fb604c6839712", [:rebar3], [], "hexpm", "b147ed22ce71d72eafdad94f055165c1c182f61a2ff49df28bcc71d1d5b94a60"}, "cowlib": {:hex, :cowlib, "2.16.0", "54592074ebbbb92ee4746c8a8846e5605052f29309d3a873468d76cdf932076f", [:make, :rebar3], [], "hexpm", "7f478d80d66b747344f0ea7708c187645cfcc08b11aa424632f78e25bf05db51"}, "credo": {:hex, :credo, "1.7.12", "9e3c20463de4b5f3f23721527fcaf16722ec815e70ff6c60b86412c695d426c1", [:mix], [{:bunt, "~> 0.2.1 or ~> 1.0", [hex: :bunt, repo: "hexpm", optional: false]}, {:file_system, "~> 0.2 or ~> 1.0", [hex: :file_system, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: false]}], "hexpm", "8493d45c656c5427d9c729235b99d498bd133421f3e0a683e5c1b561471291e5"}, - "decimal": {:hex, :decimal, "2.3.0", "3ad6255aa77b4a3c4f818171b12d237500e63525c2fd056699967a3e7ea20f62", [:mix], [], "hexpm", "a4d66355cb29cb47c3cf30e71329e58361cfcb37c34235ef3bf1d7bf3773aeac"}, - "depot": {:git, "https://github.com/agentjido/depot.git", "369beebfff4dc7835c4e6439a46351f4fba0c2aa", []}, "dialyxir": {:hex, :dialyxir, "1.4.5", "ca1571ac18e0f88d4ab245f0b60fa31ff1b12cbae2b11bd25d207f865e8ae78a", [:mix], [{:erlex, ">= 0.2.7", [hex: :erlex, repo: "hexpm", optional: false]}], "hexpm", "b0fb08bb8107c750db5c0b324fa2df5ceaa0f9307690ee3c1f6ba5b9eb5d35c3"}, - "doctor": {:hex, :doctor, "0.22.0", "223e1cace1f16a38eda4113a5c435fa9b10d804aa72d3d9f9a71c471cc958fe7", [:mix], [{:decimal, "~> 2.0", [hex: :decimal, repo: "hexpm", optional: false]}], "hexpm", "96e22cf8c0df2e9777dc55ebaa5798329b9028889c4023fed3305688d902cd5b"}, "earmark_parser": {:hex, :earmark_parser, "1.4.44", "f20830dd6b5c77afe2b063777ddbbff09f9759396500cdbe7523efd58d7a339c", [:mix], [], "hexpm", "4778ac752b4701a5599215f7030989c989ffdc4f6df457c5f36938cc2d2a2750"}, "erlex": {:hex, :erlex, "0.2.7", "810e8725f96ab74d17aac676e748627a07bc87eb950d2b83acd29dc047a30595", [:mix], [], "hexpm", "3ed95f79d1a844c3f6bf0cea61e0d5612a42ce56da9c03f01df538685365efb0"}, "eternal": {:hex, :eternal, "1.2.2", "d1641c86368de99375b98d183042dd6c2b234262b8d08dfd72b9eeaafc2a1abd", [:mix], [], "hexpm", "2c9fe32b9c3726703ba5e1d43a1d255a4f3f2d8f8f9bc19f094c7cb1a7a9e782"}, @@ -14,7 +11,6 @@ "ex_aws_s3": {:hex, :ex_aws_s3, "2.5.7", "e571424d2f345299753382f3a01b005c422b1a460a8bc3ed47659b3d3ef91e9e", [:mix], [{:ex_aws, "~> 2.0", [hex: :ex_aws, repo: "hexpm", optional: false]}, {:sweet_xml, ">= 0.0.0", [hex: :sweet_xml, repo: "hexpm", optional: true]}], "hexpm", "858e51241e50181e29aa2bc128fef548873a3a9cd580471f57eda5b64dec937f"}, "ex_doc": {:hex, :ex_doc, "0.38.2", "504d25eef296b4dec3b8e33e810bc8b5344d565998cd83914ffe1b8503737c02", [:mix], [{:earmark_parser, "~> 1.4.44", [hex: :earmark_parser, repo: "hexpm", optional: false]}, {:makeup_c, ">= 0.1.0", [hex: :makeup_c, repo: "hexpm", optional: true]}, {:makeup_elixir, "~> 0.14 or ~> 1.0", [hex: :makeup_elixir, repo: "hexpm", optional: false]}, {:makeup_erlang, "~> 0.1 or ~> 1.0", [hex: :makeup_erlang, repo: "hexpm", optional: false]}, {:makeup_html, ">= 0.1.0", [hex: :makeup_html, repo: "hexpm", optional: true]}], "hexpm", "732f2d972e42c116a70802f9898c51b54916e542cc50968ac6980512ec90f42b"}, "excoveralls": {:hex, :excoveralls, "0.18.5", "e229d0a65982613332ec30f07940038fe451a2e5b29bce2a5022165f0c9b157e", [:mix], [{:castore, "~> 1.0", [hex: :castore, repo: "hexpm", optional: true]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: false]}], "hexpm", "523fe8a15603f86d64852aab2abe8ddbd78e68579c8525ae765facc5eae01562"}, - "expublish": {:hex, :expublish, "2.7.6", "fdc15040413276d15b8de7ea3db8c390900cc36313a43c6eda1a4d567be67b8f", [:mix], [], "hexpm", "d4acca6e5db6ff26ede1950588adcd5b5e5dccb1b54d78f36abf6252983ab74b"}, "file_system": {:hex, :file_system, "1.1.0", "08d232062284546c6c34426997dd7ef6ec9f8bbd090eb91780283c9016840e8f", [:mix], [], "hexpm", "bfcf81244f416871f2a2e15c1b515287faa5db9c6bcf290222206d120b3d43f6"}, "finch": {:hex, :finch, "0.20.0", "5330aefb6b010f424dcbbc4615d914e9e3deae40095e73ab0c1bb0968933cadf", [:mix], [{:mime, "~> 1.0 or ~> 2.0", [hex: :mime, repo: "hexpm", optional: false]}, {:mint, "~> 1.6.2 or ~> 1.7", [hex: :mint, repo: "hexpm", optional: false]}, {:nimble_options, "~> 0.4 or ~> 1.0", [hex: :nimble_options, repo: "hexpm", optional: false]}, {:nimble_pool, "~> 1.1", [hex: :nimble_pool, repo: "hexpm", optional: false]}, {:telemetry, "~> 0.4 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "2658131a74d051aabfcba936093c903b8e89da9a1b63e430bee62045fa9b2ee2"}, "git_cli": {:hex, :git_cli, "0.3.0", "a5422f9b95c99483385b976f5d43f7e8233283a47cda13533d7c16131cb14df5", [:mix], [], "hexpm", "78cb952f4c86a41f4d3511f1d3ecb28edb268e3a7df278de2faa1bd4672eaf9b"}, @@ -23,14 +19,12 @@ "glob_ex": {:hex, :glob_ex, "0.1.11", "cb50d3f1ef53f6ca04d6252c7fde09fd7a1cf63387714fe96f340a1349e62c93", [:mix], [], "hexpm", "342729363056e3145e61766b416769984c329e4378f1d558b63e341020525de4"}, "gun": {:hex, :gun, "2.2.0", "b8f6b7d417e277d4c2b0dc3c07dfdf892447b087f1cc1caff9c0f556b884e33d", [:make, :rebar3], [{:cowlib, ">= 2.15.0 and < 3.0.0", [hex: :cowlib, repo: "hexpm", optional: false]}], "hexpm", "76022700c64287feb4df93a1795cff6741b83fb37415c40c34c38d2a4645261a"}, "hackney": {:hex, :hackney, "1.25.0", "390e9b83f31e5b325b9f43b76e1a785cbdb69b5b6cd4e079aa67835ded046867", [:rebar3], [{:certifi, "~> 2.15.0", [hex: :certifi, repo: "hexpm", optional: false]}, {:idna, "~> 6.1.0", [hex: :idna, repo: "hexpm", optional: false]}, {:metrics, "~> 1.0.0", [hex: :metrics, repo: "hexpm", optional: false]}, {:mimerl, "~> 1.4", [hex: :mimerl, repo: "hexpm", optional: false]}, {:parse_trans, "3.4.1", [hex: :parse_trans, repo: "hexpm", optional: false]}, {:ssl_verify_fun, "~> 1.1.0", [hex: :ssl_verify_fun, repo: "hexpm", optional: false]}, {:unicode_util_compat, "~> 0.7.1", [hex: :unicode_util_compat, repo: "hexpm", optional: false]}], "hexpm", "7209bfd75fd1f42467211ff8f59ea74d6f2a9e81cbcee95a56711ee79fd6b1d4"}, - "hako": {:git, "https://github.com/agentjido/hako.git", "1d2cff674e42d2fa8bbe42e73b8583a11e4ce14e", []}, "ham": {:hex, :ham, "0.3.0", "7cd031b4a55fba219c11553e7b13ba73bd86eab4034518445eff1e038cb9a44d", [:mix], [], "hexpm", "7d6c6b73d7a6a83233876cc1b06a4d9b5de05562b228effda4532f9a49852bf6"}, "hpax": {:hex, :hpax, "1.0.3", "ed67ef51ad4df91e75cc6a1494f851850c0bd98ebc0be6e81b026e765ee535aa", [:mix], [], "hexpm", "8eab6e1cfa8d5918c2ce4ba43588e894af35dbd8e91e6e55c817bca5847df34a"}, "httpoison": {:hex, :httpoison, "2.2.3", "a599d4b34004cc60678999445da53b5e653630651d4da3d14675fedc9dd34bd6", [:mix], [{:hackney, "~> 1.21", [hex: :hackney, repo: "hexpm", optional: false]}], "hexpm", "fa0f2e3646d3762fdc73edb532104c8619c7636a6997d20af4003da6cfc53e53"}, "idna": {:hex, :idna, "6.1.1", "8a63070e9f7d0c62eb9d9fcb360a7de382448200fbbd1b106cc96d3d8099df8d", [:rebar3], [{:unicode_util_compat, "~> 0.7.0", [hex: :unicode_util_compat, repo: "hexpm", optional: false]}], "hexpm", "92376eb7894412ed19ac475e4a86f7b413c1b9fbb5bd16dccd57934157944cea"}, "igniter": {:hex, :igniter, "0.7.2", "81c132c0df95963c7a228f74a32d3348773743ed9651f24183bfce0fe6ff16d1", [:mix], [{:glob_ex, "~> 0.1.7", [hex: :glob_ex, repo: "hexpm", optional: false]}, {:jason, "~> 1.4", [hex: :jason, repo: "hexpm", optional: false]}, {:owl, "~> 0.11", [hex: :owl, repo: "hexpm", optional: false]}, {:phx_new, "~> 1.7", [hex: :phx_new, repo: "hexpm", optional: true]}, {:req, "~> 0.5", [hex: :req, repo: "hexpm", optional: false]}, {:rewrite, ">= 1.1.1 and < 2.0.0-0", [hex: :rewrite, repo: "hexpm", optional: false]}, {:sourceror, "~> 1.4", [hex: :sourceror, repo: "hexpm", optional: false]}, {:spitfire, ">= 0.1.3 and < 1.0.0-0", [hex: :spitfire, repo: "hexpm", optional: false]}], "hexpm", "f4cab73ec31f4fb452de1a17037f8a08826105265aa2d76486fcb848189bef9b"}, "jason": {:hex, :jason, "1.4.4", "b9226785a9aa77b6857ca22832cffa5d5011a667207eb2a0ad56adb5db443b8a", [:mix], [{:decimal, "~> 1.0 or ~> 2.0", [hex: :decimal, repo: "hexpm", optional: true]}], "hexpm", "c5eb0cab91f094599f94d55bc63409236a8ec69a21a67814529e8d5f6cc90b3b"}, - "jido_shell": {:git, "https://github.com/agentjido/hako.git", "5f2d19bf9a3ed69478692be630dbc5bdf535b02b", []}, "jido_vfs": {:git, "https://github.com/agentjido/jido_vfs.git", "6aab9fb47308302e9824ed4672d69085bc2aea3f", [branch: "main"]}, "makeup": {:hex, :makeup, "1.2.1", "e90ac1c65589ef354378def3ba19d401e739ee7ee06fb47f94c687016e3713d1", [:mix], [{:nimble_parsec, "~> 1.4", [hex: :nimble_parsec, repo: "hexpm", optional: false]}], "hexpm", "d36484867b0bae0fea568d10131197a4c2e47056a6fbe84922bf6ba71c8d17ce"}, "makeup_elixir": {:hex, :makeup_elixir, "1.0.1", "e928a4f984e795e41e3abd27bfc09f51db16ab8ba1aebdba2b3a575437efafc2", [:mix], [{:makeup, "~> 1.0", [hex: :makeup, repo: "hexpm", optional: false]}, {:nimble_parsec, "~> 1.2.3 or ~> 1.3", [hex: :nimble_parsec, repo: "hexpm", optional: false]}], "hexpm", "7284900d412a3e5cfd97fdaed4f5ed389b8f2b4cb49efc0eb3bd10e2febf9507"}, @@ -40,7 +34,6 @@ "mimerl": {:hex, :mimerl, "1.4.0", "3882a5ca67fbbe7117ba8947f27643557adec38fa2307490c4c4207624cb213b", [:rebar3], [], "hexpm", "13af15f9f68c65884ecca3a3891d50a7b57d82152792f3e19d88650aa126b144"}, "mimic": {:hex, :mimic, "2.0.0", "2fb235e54dee19a71244c22b234dbf29f407f01352943f4d9f2ed1c0933d53d3", [:mix], [{:ham, "~> 0.2", [hex: :ham, repo: "hexpm", optional: false]}], "hexpm", "1d518ae1f46316c72cc0eacb1b73e1371a6965a79e3733d4ccb7349cb2a9025a"}, "mint": {:hex, :mint, "1.7.1", "113fdb2b2f3b59e47c7955971854641c61f378549d73e829e1768de90fc1abf1", [:mix], [{:castore, "~> 0.1.0 or ~> 1.0", [hex: :castore, repo: "hexpm", optional: true]}, {:hpax, "~> 0.1.1 or ~> 0.2.0 or ~> 1.0", [hex: :hpax, repo: "hexpm", optional: false]}], "hexpm", "fceba0a4d0f24301ddee3024ae116df1c3f4bb7a563a731f45fdfeb9d39a231b"}, - "mix_test_watch": {:hex, :mix_test_watch, "1.3.0", "2ffc9f72b0d1f4ecf0ce97b044e0e3c607c3b4dc21d6228365e8bc7c2856dc77", [:mix], [{:file_system, "~> 0.2 or ~> 1.0", [hex: :file_system, repo: "hexpm", optional: false]}], "hexpm", "f9e5edca976857ffac78632e635750d158df14ee2d6185a15013844af7570ffe"}, "nimble_options": {:hex, :nimble_options, "1.1.1", "e3a492d54d85fc3fd7c5baf411d9d2852922f66e69476317787a7b2bb000a61b", [:mix], [], "hexpm", "821b2470ca9442c4b6984882fe9bb0389371b8ddec4d45a9504f00a66f650b44"}, "nimble_parsec": {:hex, :nimble_parsec, "1.4.2", "8efba0122db06df95bfaa78f791344a89352ba04baedd3849593bfce4d0dc1c6", [:mix], [], "hexpm", "4b21398942dda052b403bbe1da991ccd03a053668d147d53fb8c4e0efe09c973"}, "nimble_pool": {:hex, :nimble_pool, "1.1.0", "bf9c29fbdcba3564a8b800d1eeb5a3c58f36e1e11d7b7fb2e084a643f645f06b", [:mix], [], "hexpm", "af2e4e6b34197db81f7aad230c1118eac993acc0dae6bc83bac0126d4ae0813a"},