kryth 1.0.0__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (96) hide show
  1. kryth-1.0.0/LICENSE +21 -0
  2. kryth-1.0.0/PKG-INFO +31 -0
  3. kryth-1.0.0/pyproject.toml +51 -0
  4. kryth-1.0.0/setup.cfg +4 -0
  5. kryth-1.0.0/src/agent/__init__.py +141 -0
  6. kryth-1.0.0/src/agent/_checkpoint.py +43 -0
  7. kryth-1.0.0/src/agent/_common.py +44 -0
  8. kryth-1.0.0/src/agent/_critique.py +114 -0
  9. kryth-1.0.0/src/agent/_debug.py +55 -0
  10. kryth-1.0.0/src/agent/_file_ops.py +563 -0
  11. kryth-1.0.0/src/agent/_git.py +293 -0
  12. kryth-1.0.0/src/agent/_memory.py +122 -0
  13. kryth-1.0.0/src/agent/_plan.py +22 -0
  14. kryth-1.0.0/src/agent/_project_runner.py +212 -0
  15. kryth-1.0.0/src/agent/_results.py +88 -0
  16. kryth-1.0.0/src/agent/_search.py +273 -0
  17. kryth-1.0.0/src/agent/_shell.py +231 -0
  18. kryth-1.0.0/src/agent/_specs.py +820 -0
  19. kryth-1.0.0/src/agent/_subagent.py +163 -0
  20. kryth-1.0.0/src/agent/_task_graph.py +212 -0
  21. kryth-1.0.0/src/agent/_todos.py +46 -0
  22. kryth-1.0.0/src/agent/_verify.py +26 -0
  23. kryth-1.0.0/src/agent/agent_loop.py +714 -0
  24. kryth-1.0.0/src/agent/bridge/__init__.py +115 -0
  25. kryth-1.0.0/src/agent/bridge/providers/__init__.py +35 -0
  26. kryth-1.0.0/src/agent/bridge/providers/openai_browser.py +176 -0
  27. kryth-1.0.0/src/agent/bridge/server.py +357 -0
  28. kryth-1.0.0/src/agent/context.py +63 -0
  29. kryth-1.0.0/src/agent/env.py +87 -0
  30. kryth-1.0.0/src/agent/hooks.py +68 -0
  31. kryth-1.0.0/src/agent/io.py +57 -0
  32. kryth-1.0.0/src/agent/llm.py +1193 -0
  33. kryth-1.0.0/src/agent/model_router.py +150 -0
  34. kryth-1.0.0/src/agent/permissions.py +177 -0
  35. kryth-1.0.0/src/agent/persistence.py +563 -0
  36. kryth-1.0.0/src/agent/profiles.py +237 -0
  37. kryth-1.0.0/src/agent/project_context.py +292 -0
  38. kryth-1.0.0/src/agent/prompts.py +106 -0
  39. kryth-1.0.0/src/agent/repo_index.py +262 -0
  40. kryth-1.0.0/src/agent/retriever.py +229 -0
  41. kryth-1.0.0/src/agent/session.py +154 -0
  42. kryth-1.0.0/src/agent/settings.py +86 -0
  43. kryth-1.0.0/src/agent/skill_library.py +549 -0
  44. kryth-1.0.0/src/agent/skills.py +289 -0
  45. kryth-1.0.0/src/agent/snapshots.py +193 -0
  46. kryth-1.0.0/src/agent/tools/__init__.py +141 -0
  47. kryth-1.0.0/src/agent/tools/_checkpoint.py +43 -0
  48. kryth-1.0.0/src/agent/tools/_common.py +44 -0
  49. kryth-1.0.0/src/agent/tools/_critique.py +114 -0
  50. kryth-1.0.0/src/agent/tools/_debug.py +55 -0
  51. kryth-1.0.0/src/agent/tools/_file_ops.py +563 -0
  52. kryth-1.0.0/src/agent/tools/_git.py +293 -0
  53. kryth-1.0.0/src/agent/tools/_memory.py +122 -0
  54. kryth-1.0.0/src/agent/tools/_plan.py +22 -0
  55. kryth-1.0.0/src/agent/tools/_project_runner.py +212 -0
  56. kryth-1.0.0/src/agent/tools/_results.py +88 -0
  57. kryth-1.0.0/src/agent/tools/_search.py +273 -0
  58. kryth-1.0.0/src/agent/tools/_shell.py +231 -0
  59. kryth-1.0.0/src/agent/tools/_specs.py +820 -0
  60. kryth-1.0.0/src/agent/tools/_subagent.py +163 -0
  61. kryth-1.0.0/src/agent/tools/_task_graph.py +212 -0
  62. kryth-1.0.0/src/agent/tools/_todos.py +46 -0
  63. kryth-1.0.0/src/agent/tools/_verify.py +26 -0
  64. kryth-1.0.0/src/agent/ui/__init__.py +344 -0
  65. kryth-1.0.0/src/agent/ui/activity.py +87 -0
  66. kryth-1.0.0/src/agent/ui/command_panel.py +77 -0
  67. kryth-1.0.0/src/agent/ui/components.py +417 -0
  68. kryth-1.0.0/src/agent/ui/console.py +65 -0
  69. kryth-1.0.0/src/agent/ui/diff_renderer.py +172 -0
  70. kryth-1.0.0/src/agent/ui/events.py +170 -0
  71. kryth-1.0.0/src/agent/ui/hud.py +226 -0
  72. kryth-1.0.0/src/agent/ui/input.py +213 -0
  73. kryth-1.0.0/src/agent/ui/keyread.py +85 -0
  74. kryth-1.0.0/src/agent/ui/logger.py +158 -0
  75. kryth-1.0.0/src/agent/ui/motion.py +185 -0
  76. kryth-1.0.0/src/agent/ui/panels.py +85 -0
  77. kryth-1.0.0/src/agent/ui/progress.py +68 -0
  78. kryth-1.0.0/src/agent/ui/renderer.py +462 -0
  79. kryth-1.0.0/src/agent/ui/run_summary.py +131 -0
  80. kryth-1.0.0/src/agent/ui/status_manager.py +47 -0
  81. kryth-1.0.0/src/agent/ui/streaming.py +168 -0
  82. kryth-1.0.0/src/agent/ui/summarizer.py +249 -0
  83. kryth-1.0.0/src/agent/ui/syntax.py +101 -0
  84. kryth-1.0.0/src/agent/ui/theme.py +166 -0
  85. kryth-1.0.0/src/agent/ui/updates.py +92 -0
  86. kryth-1.0.0/src/agent/validators.py +132 -0
  87. kryth-1.0.0/src/kryth/__init__.py +4 -0
  88. kryth-1.0.0/src/kryth/_repl_main.py +1123 -0
  89. kryth-1.0.0/src/kryth/config.py +371 -0
  90. kryth-1.0.0/src/kryth/main.py +109 -0
  91. kryth-1.0.0/src/kryth.egg-info/PKG-INFO +31 -0
  92. kryth-1.0.0/src/kryth.egg-info/SOURCES.txt +94 -0
  93. kryth-1.0.0/src/kryth.egg-info/dependency_links.txt +1 -0
  94. kryth-1.0.0/src/kryth.egg-info/entry_points.txt +3 -0
  95. kryth-1.0.0/src/kryth.egg-info/requires.txt +10 -0
  96. kryth-1.0.0/src/kryth.egg-info/top_level.txt +2 -0
kryth-1.0.0/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Navadeep
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
kryth-1.0.0/PKG-INFO ADDED
@@ -0,0 +1,31 @@
1
+ Metadata-Version: 2.4
2
+ Name: kryth
3
+ Version: 1.0.0
4
+ Summary: KRYTH CLI: Autonomous Coding Intelligence — terminal AI coding agent with tools, sessions, and OpenAI-compatible model support
5
+ Author: Navadeep
6
+ License-Expression: MIT
7
+ Keywords: ai,coding-agent,ai-coder,cli,openai-compatible,developer-tools
8
+ Classifier: Development Status :: 4 - Beta
9
+ Classifier: Environment :: Console
10
+ Classifier: Intended Audience :: Developers
11
+ Classifier: Programming Language :: Python :: 3
12
+ Classifier: Programming Language :: Python :: 3 :: Only
13
+ Classifier: Programming Language :: Python :: 3.9
14
+ Classifier: Programming Language :: Python :: 3.10
15
+ Classifier: Programming Language :: Python :: 3.11
16
+ Classifier: Programming Language :: Python :: 3.12
17
+ Classifier: Topic :: Software Development
18
+ Classifier: Topic :: Terminals
19
+ Requires-Python: >=3.9
20
+ Description-Content-Type: text/markdown
21
+ License-File: LICENSE
22
+ Requires-Dist: openai>=1.0.0
23
+ Requires-Dist: prompt_toolkit>=3.0.0
24
+ Requires-Dist: rich>=13.0.0
25
+ Requires-Dist: python-dotenv>=1.0.0
26
+ Provides-Extra: bridge
27
+ Requires-Dist: fastapi>=0.110.0; extra == "bridge"
28
+ Requires-Dist: uvicorn>=0.29.0; extra == "bridge"
29
+ Requires-Dist: websockets>=12.0; extra == "bridge"
30
+ Requires-Dist: playwright>=1.44.0; extra == "bridge"
31
+ Dynamic: license-file
@@ -0,0 +1,51 @@
1
+ [build-system]
2
+ requires = ["setuptools>=61"]
3
+ build-backend = "setuptools.build_meta"
4
+
5
+ [project]
6
+ name = "kryth"
7
+ version = "1.0.0"
8
+ description = "KRYTH CLI: Autonomous Coding Intelligence — terminal AI coding agent with tools, sessions, and OpenAI-compatible model support"
9
+ readme = "README.md"
10
+ license = "MIT"
11
+ requires-python = ">=3.9"
12
+ authors = [
13
+ { name = "Navadeep" }
14
+ ]
15
+ keywords = ["ai", "coding-agent", "ai-coder", "cli", "openai-compatible", "developer-tools"]
16
+ classifiers = [
17
+ "Development Status :: 4 - Beta",
18
+ "Environment :: Console",
19
+ "Intended Audience :: Developers",
20
+ "Programming Language :: Python :: 3",
21
+ "Programming Language :: Python :: 3 :: Only",
22
+ "Programming Language :: Python :: 3.9",
23
+ "Programming Language :: Python :: 3.10",
24
+ "Programming Language :: Python :: 3.11",
25
+ "Programming Language :: Python :: 3.12",
26
+ "Topic :: Software Development",
27
+ "Topic :: Terminals",
28
+ ]
29
+
30
+ dependencies = [
31
+ "openai>=1.0.0",
32
+ "prompt_toolkit>=3.0.0",
33
+ "rich>=13.0.0",
34
+ "python-dotenv>=1.0.0",
35
+ ]
36
+
37
+ [project.optional-dependencies]
38
+ bridge = [
39
+ "fastapi>=0.110.0",
40
+ "uvicorn>=0.29.0",
41
+ "websockets>=12.0",
42
+ "playwright>=1.44.0",
43
+ ]
44
+
45
+ [project.scripts]
46
+
47
+ kryth = "kryth.main:main"
48
+ kryth-cli = "kryth.main:main"
49
+
50
+ [tool.setuptools.packages.find]
51
+ where = ["src"]
kryth-1.0.0/setup.cfg ADDED
@@ -0,0 +1,4 @@
1
+ [egg_info]
2
+ tag_build =
3
+ tag_date = 0
4
+
@@ -0,0 +1,141 @@
1
+ """Tool registry for the agent loop.
2
+
3
+ Public surface:
4
+
5
+ - ``TOOLS`` — name → callable. Used by the dispatcher.
6
+ - ``TOOL_SPECS`` — JSON-schema list passed to the LLM via ``tools=``.
7
+ - ``READ_ONLY_TOOLS`` — names safe in plan mode.
8
+ - ``RUN_COMMAND_ERROR_MARKER`` — sentinel used to flag non-zero exits.
9
+
10
+ Implementations live in private submodules (``_file_ops``, ``_search``,
11
+ ``_shell``, ``_todos``, ``_plan``, ``_subagent``). ``_specs`` carries
12
+ the LLM-facing schemas. The registry is assembled here so callers can
13
+ keep doing ``from agent.tools import TOOLS, TOOL_SPECS``.
14
+ """
15
+
16
+ from __future__ import annotations
17
+
18
+ from agent.tools._common import RUN_COMMAND_ERROR_MARKER
19
+ from agent.tools._file_ops import (
20
+ read_file,
21
+ write_file,
22
+ edit_file,
23
+ multi_edit,
24
+ delete_file,
25
+ list_files,
26
+ rollback_file,
27
+ )
28
+ from agent.tools._memory import add_memory
29
+ from agent.tools._search import (
30
+ search_code,
31
+ grep,
32
+ glob_files,
33
+ semantic_search,
34
+ lookup_symbol,
35
+ lookup_imports,
36
+ lookup_dependents,
37
+ )
38
+ from agent.tools._shell import run_command, task_output
39
+ from agent.tools._todos import todo_write, todo_read
40
+ from agent.tools._plan import exit_plan_mode
41
+ from agent.tools._subagent import spawn_agent, spawn_agents_parallel
42
+ from agent.tools._critique import self_critique
43
+ from agent.tools._git import git_op
44
+ from agent.tools._verify import verify_files
45
+ from agent.tools._debug import diagnose_error
46
+ from agent.tools._project_runner import run_tests, run_install
47
+ from agent.tools._checkpoint import checkpoint
48
+ from agent.tools._task_graph import run_task_graph
49
+ from agent.tools._specs import TOOL_SPECS
50
+
51
+
52
+ TOOLS = {
53
+ "read_file": read_file,
54
+ "write_file": write_file,
55
+ "edit_file": edit_file,
56
+ "multi_edit": multi_edit,
57
+ "delete_file": delete_file,
58
+ "list_files": list_files,
59
+ "rollback_file": rollback_file,
60
+ "run_command": run_command,
61
+ "task_output": task_output,
62
+ "search_code": search_code,
63
+ "grep": grep,
64
+ "glob": glob_files,
65
+ "semantic_search": semantic_search,
66
+ "lookup_symbol": lookup_symbol,
67
+ "lookup_imports": lookup_imports,
68
+ "lookup_dependents": lookup_dependents,
69
+ "todo_write": todo_write,
70
+ "todo_read": todo_read,
71
+ "exit_plan_mode": exit_plan_mode,
72
+ "spawn_agent": spawn_agent,
73
+ "spawn_agents_parallel": spawn_agents_parallel,
74
+ "self_critique": self_critique,
75
+ "git_op": git_op,
76
+ "verify_files": verify_files,
77
+ "diagnose_error": diagnose_error,
78
+ "run_tests": run_tests,
79
+ "run_install": run_install,
80
+ "checkpoint": checkpoint,
81
+ "run_task_graph": run_task_graph,
82
+ "add_memory": add_memory,
83
+ }
84
+
85
+
86
+ READ_ONLY_TOOLS = {
87
+ "read_file",
88
+ "list_files",
89
+ "search_code",
90
+ "grep",
91
+ "glob",
92
+ "semantic_search",
93
+ "lookup_symbol",
94
+ "lookup_imports",
95
+ "lookup_dependents",
96
+ "todo_read",
97
+ "todo_write",
98
+ "task_output",
99
+ "exit_plan_mode",
100
+ "self_critique",
101
+ "verify_files",
102
+ "diagnose_error",
103
+ }
104
+
105
+
106
+ # Tools that emit their own visual representation (panel, diff, todo
107
+ # list) and don't need the generic "⎿ first-line preview" tee under
108
+ # their tool header. The agent loop reads this set when deciding
109
+ # whether to emit ``tool_result``.
110
+ SELF_RENDERED_TOOLS = {
111
+ "write_file",
112
+ "edit_file",
113
+ "multi_edit",
114
+ "run_command",
115
+ "todo_write",
116
+ "exit_plan_mode",
117
+ }
118
+
119
+
120
+ # Sanity check at import: the spec list and the registry must agree.
121
+ # Drift here would silently advertise non-existent tools to the model
122
+ # (or hide real ones), so we fail fast instead.
123
+ _spec_names = {t["function"]["name"] for t in TOOL_SPECS}
124
+ _registry_names = set(TOOLS.keys())
125
+ if _spec_names != _registry_names:
126
+ missing_specs = _registry_names - _spec_names
127
+ missing_tools = _spec_names - _registry_names
128
+ raise RuntimeError(
129
+ "tools registry / specs mismatch — "
130
+ f"in TOOLS but not TOOL_SPECS: {sorted(missing_specs)}; "
131
+ f"in TOOL_SPECS but not TOOLS: {sorted(missing_tools)}"
132
+ )
133
+
134
+
135
+ __all__ = [
136
+ "TOOLS",
137
+ "TOOL_SPECS",
138
+ "READ_ONLY_TOOLS",
139
+ "SELF_RENDERED_TOOLS",
140
+ "RUN_COMMAND_ERROR_MARKER",
141
+ ]
@@ -0,0 +1,43 @@
1
+ """Milestone-checkpoint tool.
2
+
3
+ Lets the agent record "I just finished phase X" markers into the
4
+ persisted session transcript. Useful for long-horizon tasks: the
5
+ operator can scan checkpoints on ``/resume`` and see structured
6
+ milestones instead of scrolling through raw tool calls.
7
+ """
8
+
9
+ from __future__ import annotations
10
+
11
+ from agent.tools._results import err
12
+
13
+
14
+ def checkpoint(label, summary: str = "", modified_files=None):
15
+ """Write a milestone marker into the session transcript.
16
+
17
+ ``label`` — short title (kebab-case recommended, max 120 chars).
18
+ ``summary`` — one paragraph describing what was just completed
19
+ and any decisions worth remembering on resume.
20
+ ``modified_files`` — optional list of paths touched by this milestone.
21
+ """
22
+ if not isinstance(label, str) or not label.strip():
23
+ return err("BAD_ARGS", "checkpoint: label must be a non-empty string")
24
+
25
+ if modified_files is not None and not isinstance(modified_files, list):
26
+ return err("BAD_ARGS", "checkpoint: modified_files must be a list of strings")
27
+
28
+ from agent.persistence import session_store
29
+ store = session_store()
30
+ cp = store.append_checkpoint(
31
+ label=label.strip(),
32
+ summary=str(summary or ""),
33
+ modified_files=[p for p in (modified_files or []) if isinstance(p, str)],
34
+ )
35
+ if cp is None:
36
+ return "(checkpoint not persisted — session store unavailable)"
37
+ return (
38
+ f"checkpoint recorded: {cp['label']!s} at turn {cp['turn_index']}"
39
+ + (f" ({len(cp['modified_files'])} files)" if cp.get("modified_files") else "")
40
+ )
41
+
42
+
43
+ __all__ = ["checkpoint"]
@@ -0,0 +1,44 @@
1
+ """Shared helpers used across tool implementations."""
2
+
3
+ from __future__ import annotations
4
+
5
+ from agent.tools._results import TOOL_ERROR_PREFIX, err
6
+
7
+
8
+ # Legacy alias. New code should call ``err("NON_ZERO_EXIT", ...)`` from
9
+ # ``_results``; this name remains so the rest of the codebase (and any
10
+ # saved transcripts) can still detect "a tool error happened" by
11
+ # substring match. Both the new ``[ERROR ...]`` prefix and historical
12
+ # ``[NON_ZERO_EXIT]`` results match it.
13
+ RUN_COMMAND_ERROR_MARKER = TOOL_ERROR_PREFIX
14
+
15
+ # Max chars stored for a single tool result. Head + tail are preserved
16
+ # so both setup context and error tails (stack traces, last-test-failure
17
+ # line) survive trimming. read_file is exempt — it has its own
18
+ # offset/limit pagination.
19
+ TOOL_OUTPUT_BUDGET = 3000
20
+
21
+
22
+ def trim_head_tail(text: str, budget: int = TOOL_OUTPUT_BUDGET) -> str:
23
+ """Trim long output to head + tail with an elision marker.
24
+
25
+ Returns ``text`` unchanged when under budget. Otherwise keeps the
26
+ first ``budget // 2`` chars and the last ``budget // 2`` chars,
27
+ joined by a single-line marker showing how much was dropped.
28
+ """
29
+ if len(text) <= budget:
30
+ return text
31
+ half = budget // 2
32
+ dropped = len(text) - 2 * half
33
+ head = text[:half]
34
+ tail = text[-half:]
35
+ return f"{head}\n...[truncated {dropped} chars]...\n{tail}"
36
+
37
+
38
+ __all__ = [
39
+ "RUN_COMMAND_ERROR_MARKER",
40
+ "TOOL_ERROR_PREFIX",
41
+ "TOOL_OUTPUT_BUDGET",
42
+ "err",
43
+ "trim_head_tail",
44
+ ]
@@ -0,0 +1,114 @@
1
+ """Self-critique tool.
2
+
3
+ Lets the agent pause after a non-trivial edit batch and ask a cheap
4
+ reviewer model to grade its own work. The reviewer reads a unified diff
5
+ between the most recent snapshot and the current file (or the diff for
6
+ a list of files) and returns ``[BUG]`` / ``[RISK]`` / ``[SUSPECT]``
7
+ findings — or ``LGTM`` if nothing stands out.
8
+
9
+ Why this is its own tool rather than a hook on every write:
10
+ - Critique costs a model call. Auto-running on every micro-edit would
11
+ burn quota.
12
+ - The agent chooses when it has reached a meaningful checkpoint
13
+ (feature done, refactor finished). That's the right granularity.
14
+ """
15
+
16
+ from __future__ import annotations
17
+
18
+ import difflib
19
+ import os
20
+ from pathlib import Path
21
+
22
+ from agent import snapshots
23
+ from agent.tools._results import err
24
+
25
+
26
+ def _read_text(path: str) -> str | None:
27
+ try:
28
+ with open(path, "r", encoding="utf-8") as f:
29
+ return f.read()
30
+ except (OSError, UnicodeDecodeError):
31
+ return None
32
+
33
+
34
+ def _diff_against_snapshot(path: str) -> tuple[str, str]:
35
+ """Return ``(diff_text, status)`` for ``path``.
36
+
37
+ ``status`` describes what kind of comparison happened: ``ok``,
38
+ ``no-snapshot``, ``unreadable``. The diff is empty whenever the file
39
+ contents match the latest snapshot (no change to review).
40
+ """
41
+ backups = snapshots.list_snapshots(path)
42
+ current = _read_text(path)
43
+ if current is None:
44
+ return "", "unreadable"
45
+ if not backups:
46
+ return "", "no-snapshot"
47
+
48
+ backup_path = backups[0]["backup_path"]
49
+ try:
50
+ with open(backup_path, "r", encoding="utf-8") as f:
51
+ previous = f.read()
52
+ except (OSError, UnicodeDecodeError):
53
+ return "", "unreadable"
54
+
55
+ if previous == current:
56
+ return "", "unchanged"
57
+
58
+ diff = "\n".join(difflib.unified_diff(
59
+ previous.splitlines(),
60
+ current.splitlines(),
61
+ fromfile=f"a/{path}",
62
+ tofile=f"b/{path}",
63
+ lineterm="",
64
+ ))
65
+ return diff, "ok"
66
+
67
+
68
+ def self_critique(paths, intent: str = ""):
69
+ """Ask the reviewer model to grade recent edits to ``paths``.
70
+
71
+ ``paths`` may be a single path or a list. Each file is diffed against
72
+ its most recent snapshot; the combined diff is sent to the critic.
73
+
74
+ ``intent`` is a one-line description of the goal the edits were
75
+ meant to achieve — gives the reviewer the success criterion to
76
+ measure against.
77
+ """
78
+ from agent.llm import critique
79
+
80
+ if isinstance(paths, str):
81
+ path_list = [paths]
82
+ elif isinstance(paths, list):
83
+ path_list = [p for p in paths if isinstance(p, str) and p.strip()]
84
+ else:
85
+ return err(
86
+ "BAD_ARGS",
87
+ "self_critique: paths must be a string or list of strings",
88
+ )
89
+
90
+ if not path_list:
91
+ return err("BAD_ARGS", "self_critique: paths is empty")
92
+
93
+ diffs: list[str] = []
94
+ statuses: dict[str, str] = {}
95
+ for p in path_list:
96
+ d, status = _diff_against_snapshot(p)
97
+ statuses[p] = status
98
+ if d:
99
+ diffs.append(d)
100
+
101
+ if not diffs:
102
+ nothing = ", ".join(f"{p}={statuses[p]}" for p in path_list)
103
+ return f"(no diff to review — {nothing})"
104
+
105
+ combined = "\n".join(diffs)
106
+ findings = critique(combined, intent=intent)
107
+ if not findings:
108
+ return "(critique unavailable — proceeding without review)"
109
+
110
+ header = f"critic reviewed {len(diffs)} file(s) vs latest snapshot:"
111
+ return header + "\n" + findings
112
+
113
+
114
+ __all__ = ["self_critique"]
@@ -0,0 +1,55 @@
1
+ """Autonomous-debug helper tool.
2
+
3
+ Lets the agent ask a cheap reviewer model to triage a failed command —
4
+ "what's broken and what should I try next" — without burning the main
5
+ model's context on long error traces. The agent stays in the driver's
6
+ seat: this tool gives a diagnosis, the agent decides what to do.
7
+ """
8
+
9
+ from __future__ import annotations
10
+
11
+ from agent.tools._results import err
12
+
13
+
14
+ def diagnose_error(
15
+ command,
16
+ stdout: str = "",
17
+ stderr: str = "",
18
+ exit_code: int = 1,
19
+ context_hint: str = "",
20
+ ):
21
+ """Diagnose a failed command. Returns the model's CAUSE/FIX/CONFIDENCE
22
+ block, or a structured error if inputs are unusable.
23
+
24
+ ``command`` — the literal command that ran (string).
25
+ ``stdout/stderr`` — captured output; tails are kept, head dropped.
26
+ ``exit_code`` — the non-zero exit code.
27
+ ``context_hint`` — optional one-liner: what the agent was trying
28
+ to accomplish, so the diagnosis isn't context-free.
29
+ """
30
+ if not isinstance(command, str) or not command.strip():
31
+ return err("BAD_ARGS", "diagnose_error: command must be a non-empty string")
32
+
33
+ try:
34
+ ec = int(exit_code)
35
+ except (TypeError, ValueError):
36
+ return err("BAD_ARGS", "diagnose_error: exit_code must be an integer")
37
+
38
+ if ec == 0 and not (stderr or "").strip():
39
+ return "(command appears to have succeeded — nothing to diagnose)"
40
+
41
+ from agent.llm import diagnose_error as _ask
42
+
43
+ findings = _ask(
44
+ command=command.strip(),
45
+ stdout=str(stdout or ""),
46
+ stderr=str(stderr or ""),
47
+ exit_code=ec,
48
+ context_hint=str(context_hint or ""),
49
+ )
50
+ if not findings:
51
+ return "(diagnosis unavailable — proceed manually)"
52
+ return findings
53
+
54
+
55
+ __all__ = ["diagnose_error"]