jac-coder 0.1.0__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (85) hide show
  1. jac_coder/__init__.jac +0 -0
  2. jac_coder/api.jac +82 -0
  3. jac_coder/cli_entry.py +25 -0
  4. jac_coder/config.jac +36 -0
  5. jac_coder/context.jac +17 -0
  6. jac_coder/data/examples/ai_agent.md +90 -0
  7. jac_coder/data/examples/blog_app.md +386 -0
  8. jac_coder/data/examples/core_patterns.md +321 -0
  9. jac_coder/data/examples/todo_app.md +321 -0
  10. jac_coder/data/reference/ai.md +131 -0
  11. jac_coder/data/reference/backend.md +215 -0
  12. jac_coder/data/reference/frontend.md +271 -0
  13. jac_coder/data/reference/osp.md +229 -0
  14. jac_coder/data/reference/pitfalls.md +141 -0
  15. jac_coder/data/reference/syntax.md +159 -0
  16. jac_coder/data/rules/core_jac.md +559 -0
  17. jac_coder/data/rules/fullstack.md +362 -0
  18. jac_coder/data/rules/workflow.md +88 -0
  19. jac_coder/events.jac +110 -0
  20. jac_coder/impl/api.impl.jac +399 -0
  21. jac_coder/impl/config.impl.jac +163 -0
  22. jac_coder/impl/context.impl.jac +117 -0
  23. jac_coder/impl/mcp_manager.impl.jac +380 -0
  24. jac_coder/impl/memory.impl.jac +247 -0
  25. jac_coder/impl/nodes.impl.jac +259 -0
  26. jac_coder/impl/permission.impl.jac +62 -0
  27. jac_coder/impl/walkers.impl.jac +298 -0
  28. jac_coder/mcp_manager.jac +35 -0
  29. jac_coder/memory.jac +15 -0
  30. jac_coder/nodes.jac +306 -0
  31. jac_coder/permission.jac +19 -0
  32. jac_coder/serve_entry.jac +30 -0
  33. jac_coder/server.jac +324 -0
  34. jac_coder/tool/__init__.jac +17 -0
  35. jac_coder/tool/checked.jac +10 -0
  36. jac_coder/tool/delegation.jac +23 -0
  37. jac_coder/tool/filesystem.jac +25 -0
  38. jac_coder/tool/git.jac +18 -0
  39. jac_coder/tool/guarded.jac +23 -0
  40. jac_coder/tool/impl/checked.impl.jac +38 -0
  41. jac_coder/tool/impl/delegation.impl.jac +157 -0
  42. jac_coder/tool/impl/filesystem.impl.jac +781 -0
  43. jac_coder/tool/impl/git.impl.jac +115 -0
  44. jac_coder/tool/impl/guarded.impl.jac +72 -0
  45. jac_coder/tool/impl/jac_analyzer.impl.jac +593 -0
  46. jac_coder/tool/impl/jac_docs.impl.jac +136 -0
  47. jac_coder/tool/impl/jac_tools.impl.jac +79 -0
  48. jac_coder/tool/impl/mcp.impl.jac +32 -0
  49. jac_coder/tool/impl/preview.impl.jac +233 -0
  50. jac_coder/tool/impl/question.impl.jac +29 -0
  51. jac_coder/tool/impl/scaffold.impl.jac +231 -0
  52. jac_coder/tool/impl/search.impl.jac +85 -0
  53. jac_coder/tool/impl/shell.impl.jac +89 -0
  54. jac_coder/tool/impl/task.impl.jac +12 -0
  55. jac_coder/tool/impl/think.impl.jac +4 -0
  56. jac_coder/tool/impl/todo.impl.jac +58 -0
  57. jac_coder/tool/impl/validate.impl.jac +236 -0
  58. jac_coder/tool/impl/web.impl.jac +91 -0
  59. jac_coder/tool/jac_analyzer.jac +21 -0
  60. jac_coder/tool/jac_docs.jac +9 -0
  61. jac_coder/tool/jac_tools.jac +11 -0
  62. jac_coder/tool/mcp.jac +17 -0
  63. jac_coder/tool/preview.jac +31 -0
  64. jac_coder/tool/question.jac +7 -0
  65. jac_coder/tool/scaffold.jac +10 -0
  66. jac_coder/tool/search.jac +14 -0
  67. jac_coder/tool/shell.jac +12 -0
  68. jac_coder/tool/task.jac +9 -0
  69. jac_coder/tool/think.jac +5 -0
  70. jac_coder/tool/todo.jac +12 -0
  71. jac_coder/tool/validate.jac +11 -0
  72. jac_coder/tool/vision.jac +17 -0
  73. jac_coder/tool/web.jac +10 -0
  74. jac_coder/util/__init__.jac +18 -0
  75. jac_coder/util/colors.jac +20 -0
  76. jac_coder/util/impl/sandbox.impl.jac +38 -0
  77. jac_coder/util/impl/tool_output.impl.jac +208 -0
  78. jac_coder/util/sandbox.jac +8 -0
  79. jac_coder/util/tool_output.jac +29 -0
  80. jac_coder/walkers.jac +67 -0
  81. jac_coder-0.1.0.dist-info/METADATA +9 -0
  82. jac_coder-0.1.0.dist-info/RECORD +85 -0
  83. jac_coder-0.1.0.dist-info/WHEEL +5 -0
  84. jac_coder-0.1.0.dist-info/entry_points.txt +3 -0
  85. jac_coder-0.1.0.dist-info/top_level.txt +1 -0
@@ -0,0 +1,10 @@
1
+ import from jac_coder.tool.guarded { write_file_guarded, edit_file_guarded }
2
+
3
+
4
+ """Write a file with auto-validation."""
5
+ def write_code(file_path: str, content: str) -> str;
6
+
7
+ """Find-and-replace with auto-validation."""
8
+ def edit_code(
9
+ file_path: str, old_string: str, new_string: str, replace_all: bool = False
10
+ ) -> str;
@@ -0,0 +1,23 @@
1
+ import threading;
2
+
3
+ import from jac_coder.config { get_config }
4
+ import from jac_coder.util.tool_output { tool_status, tool_end }
5
+ import from jac_coder.events { track_file, track_error, emit_event }
6
+
7
+
8
+ glob _tl: Any = threading.local();
9
+
10
+
11
+ def init_budget(total: int = 90) -> None;
12
+ def get_remaining_budget() -> int;
13
+ def consume_budget(used: int) -> None;
14
+ def get_recent_results_summary() -> str;
15
+ def get_project_summary_for_subagent() -> str;
16
+
17
+
18
+ """Spawn a SubAgent for a task."""
19
+ def spawn_agent(task: str, mode: str = "worker") -> str;
20
+
21
+ sem spawn_agent = "Delegate a task to a focused SubAgent. mode='worker' for code changes (read+write), 'explorer' for investigation (read-only). Write a detailed instruction string including file paths, patterns to follow, and what success looks like.";
22
+ sem spawn_agent.task = "Detailed instruction for the SubAgent — include file paths, context, and success criteria";
23
+ sem spawn_agent.mode = "Capability scope: 'worker' (can modify files) or 'explorer' (read-only)";
@@ -0,0 +1,25 @@
1
+ import os;
2
+ import re;
3
+ import time;
4
+ import subprocess;
5
+ import mimetypes;
6
+
7
+ import from pathlib { Path }
8
+ import from jac_coder.events { emit_event }
9
+ import from jac_coder.util.sandbox { sandbox_path }
10
+ import from jac_coder.util.tool_output { truncate_output, tool_status, tool_end }
11
+
12
+
13
+ """Read a file with line numbers."""
14
+ def read_file(file_path: str, offset: int = 0, limit: int = 2000) -> str;
15
+
16
+ """Write content to a file."""
17
+ def write_file(file_path: str, content: str) -> str;
18
+
19
+ """Find-and-replace in a file."""
20
+ def edit_file(
21
+ file_path: str, old_string: str, new_string: str, replace_all: bool = False
22
+ ) -> str;
23
+
24
+ """List files matching a glob pattern."""
25
+ def list_files(directory: str = ".", pattern: str = "**/*") -> str;
jac_coder/tool/git.jac ADDED
@@ -0,0 +1,18 @@
1
+ import os;
2
+ import subprocess;
3
+
4
+ import from jac_coder.util.tool_output { truncate_output, tool_status, tool_end }
5
+ import from jac_coder.util.sandbox { sandbox_path }
6
+
7
+
8
+ """Show working tree status."""
9
+ def git_status() -> str;
10
+
11
+ """Show diff of changes."""
12
+ def git_diff(ref: str = "HEAD") -> str;
13
+
14
+ """Stage files and create a commit."""
15
+ def git_commit(message: str, files: list[str] = []) -> str;
16
+
17
+ """Show recent commit history."""
18
+ def git_log(count: int = 10) -> str;
@@ -0,0 +1,23 @@
1
+ import os;
2
+
3
+ import from jac_coder.tool.shell { bash_exec }
4
+ import from jac_coder.util.colors { YELLOW, DIM, RESET }
5
+ import from jac_coder.permission { permission_engine }
6
+ import from jac_coder.tool.filesystem { write_file, edit_file }
7
+
8
+
9
+ def _check_permission(tool_id: str, resource: str) -> str;
10
+ def _ask_user(tool_id: str, resource: str) -> str;
11
+
12
+ """Write a file with permission check."""
13
+ def write_file_guarded(file_path: str, content: str) -> str;
14
+
15
+ """Find-and-replace with permission check."""
16
+ def edit_file_guarded(
17
+ file_path: str, old_string: str, new_string: str, replace_all: bool = False
18
+ ) -> str;
19
+
20
+ """Run a shell command with permission check."""
21
+ def run_command(
22
+ command: str, workdir: str = ".", timeout: int = 120, background: bool = False
23
+ ) -> str;
@@ -0,0 +1,38 @@
1
+ def _enforce_check(result: str, file_path: str) -> str {
2
+ if "Error:" in result
3
+ and (
4
+ "Permission denied" in result
5
+ or "File not found" in result
6
+ or "old_string not found" in result
7
+ ) {
8
+ return result;
9
+ }
10
+
11
+ if not file_path.endswith(".jac") {
12
+ return result;
13
+ }
14
+
15
+ if "VALIDATION ERRORS" in result {
16
+ return result + "\n\n[ACTION REQUIRED: Call jac_docs(query) to look up the correct Jac syntax BEFORE attempting to fix. Do NOT guess — look it up first, then fix.]";
17
+ }
18
+
19
+ if "RUNTIME ERRORS" in result {
20
+ return result + "\n\n[ACTION REQUIRED: Call jac_docs(query) to look up the correct Jac syntax BEFORE attempting to fix. Do NOT guess — look it up first, then fix.]";
21
+ }
22
+
23
+ return result;
24
+ }
25
+
26
+
27
+ impl write_code(file_path: str, content: str) -> str {
28
+ result = write_file_guarded(file_path, content);
29
+ return _enforce_check(result, file_path);
30
+ }
31
+
32
+
33
+ impl edit_code(
34
+ file_path: str, old_string: str, new_string: str, replace_all: bool = False
35
+ ) -> str {
36
+ result = edit_file_guarded(file_path, old_string, new_string, replace_all);
37
+ return _enforce_check(result, file_path);
38
+ }
@@ -0,0 +1,157 @@
1
+ impl init_budget(total: int = 90) -> None {
2
+ _tl.budget = total;
3
+ _tl.budget_initialized = True;
4
+ _tl.recent_results = [];
5
+ }
6
+
7
+
8
+ impl get_remaining_budget() -> int {
9
+ return getattr(_tl, "budget", 0);
10
+ }
11
+
12
+
13
+ impl consume_budget(used: int) -> None {
14
+ _tl.budget = max(0, getattr(_tl, "budget", 0) - used);
15
+ }
16
+
17
+
18
+ impl get_recent_results_summary() -> str {
19
+ results = getattr(_tl, "recent_results", []);
20
+ if not results {
21
+ return "";
22
+ }
23
+ parts: list = [];
24
+ for r in results[-3:] {
25
+ line = f"- [{r.get('mode', 'worker')}] {r.get('task_preview', '')[:100]}";
26
+ fmod = r.get("files_modified", []);
27
+ if fmod {
28
+ line += f" -> modified: {', '.join(fmod)}";
29
+ }
30
+ errs = r.get("errors", []);
31
+ if errs {
32
+ line += f" -> errors: {', '.join(errs[:2])}";
33
+ }
34
+ parts.append(line);
35
+ }
36
+ return "Previous SubAgent results:\n" + "\n".join(parts);
37
+ }
38
+
39
+
40
+ impl get_project_summary_for_subagent() -> str {
41
+ try {
42
+ import jac_coder.nodes as _nodes;
43
+ sessions = [root()-->(?:(_nodes.Session))](?status=="active");
44
+ if sessions {
45
+ sessions.sort(key=lambda s : s.updated_at, reverse=True);
46
+ proj_summary = sessions[0].project_summary;
47
+ if proj_summary {
48
+ return proj_summary;
49
+ }
50
+ }
51
+ } except Exception { }
52
+ return "";
53
+ }
54
+
55
+
56
+ impl spawn_agent(task: str, mode: str = "worker") -> str {
57
+ tool_status("spawn_agent", f"[{mode}] {task[:80]}...");
58
+
59
+ if not task {
60
+ tool_end("-> no task", error=True);
61
+ return "Error: task description is required.";
62
+ }
63
+
64
+ if mode not in ["worker", "explorer"] {
65
+ mode = "worker";
66
+ }
67
+
68
+ # Initialize per-thread budget on first SubAgent call in this chat() turn
69
+ if not getattr(_tl, "budget_initialized", False) {
70
+ cfg = get_config();
71
+ init_budget(cfg.max_react_iterations * 3);
72
+ }
73
+ remaining = get_remaining_budget();
74
+ if remaining <= 0 {
75
+ tool_end("-> budget exhausted", error=True);
76
+ return "Error: Iteration budget exhausted. Cannot spawn more SubAgents.";
77
+ }
78
+
79
+ # Enrich task with session context
80
+ project_ctx = get_project_summary_for_subagent();
81
+ recent_work = get_recent_results_summary();
82
+
83
+ enriched_parts: list = [task];
84
+ if project_ctx {
85
+ enriched_parts.append(f"\n\n--- Project Context ---\n{project_ctx}");
86
+ }
87
+ if recent_work {
88
+ enriched_parts.append(f"\n\n--- Recent SubAgent Work ---\n{recent_work}");
89
+ }
90
+ enriched_task = "".join(enriched_parts);
91
+
92
+ # Spawn SubAgent walker on the MainAgent node
93
+ import from jac_coder.nodes { WorkerAgent, ExplorerAgent }
94
+ import from jac_coder.walkers { ensure_main_agent }
95
+ ;
96
+
97
+ try {
98
+ main_agent = ensure_main_agent();
99
+
100
+ # Spawn the walker on MainAgent — entry ability handles streaming + event forwarding
101
+ if mode == "worker" {
102
+ result = main_agent spawn WorkerAgent(task=enriched_task);
103
+ } else {
104
+ result = main_agent spawn ExplorerAgent(task=enriched_task);
105
+ }
106
+
107
+ # Extract result from walker's has fields
108
+ content = result.result_content;
109
+ used_tools = result.result_tools;
110
+ fmod = result.result_files;
111
+
112
+ has_errs = False;
113
+ iters_used = len(used_tools) if used_tools else 5;
114
+
115
+ # Track budget and state
116
+ consume_budget(iters_used);
117
+ for fp in fmod {
118
+ track_file(fp);
119
+ }
120
+ if has_errs {
121
+ track_error();
122
+ }
123
+
124
+ # Record result for subsequent SubAgents in this turn
125
+ _tl.recent_results.append(
126
+ {
127
+ "mode": mode,
128
+ "task_preview": task[:200],
129
+ "files_modified": fmod,
130
+ "errors": ["error"] if has_errs else [],
131
+ "content_preview": content[:300] if content else ""
132
+ }
133
+ );
134
+
135
+ # Build result string
136
+ result_parts: list = [];
137
+ if content {
138
+ result_parts.append(content);
139
+ }
140
+ if fmod {
141
+ result_parts.append(f"\nFiles modified: {', '.join(fmod)}");
142
+ }
143
+ if has_errs {
144
+ result_parts.append(
145
+ "\nNote: Some errors were encountered during execution."
146
+ );
147
+ }
148
+
149
+ tool_end(f"-> {mode} done, {len(fmod)} files");
150
+ return "\n".join(result_parts) if result_parts else "SubAgent completed.";
151
+ } except Exception as e {
152
+ consume_budget(5);
153
+ tool_end(f"-> error: {e}", error=True);
154
+ track_error();
155
+ return f"Error: SubAgent failed - {str(e)}";
156
+ }
157
+ }