jac-coder 0.2.7__tar.gz → 0.2.8__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 (101) hide show
  1. {jac_coder-0.2.7 → jac_coder-0.2.8}/PKG-INFO +1 -2
  2. {jac_coder-0.2.7 → jac_coder-0.2.8}/jac_coder/api.impl.jac +12 -3
  3. {jac_coder-0.2.7 → jac_coder-0.2.8}/jac_coder/core/nodes.impl.jac +18 -1
  4. {jac_coder-0.2.7 → jac_coder-0.2.8}/jac_coder/core/nodes.jac +214 -14
  5. {jac_coder-0.2.7 → jac_coder-0.2.8}/jac_coder/core/walkers.impl.jac +43 -0
  6. {jac_coder-0.2.7 → jac_coder-0.2.8}/jac_coder/core/walkers.jac +2 -2
  7. {jac_coder-0.2.7 → jac_coder-0.2.8}/jac_coder/infra/config.jac +1 -1
  8. {jac_coder-0.2.7 → jac_coder-0.2.8}/jac_coder/runtime/prompt.impl.jac +9 -0
  9. {jac_coder-0.2.7 → jac_coder-0.2.8}/jac_coder/runtime/prompt.jac +18 -1
  10. {jac_coder-0.2.7 → jac_coder-0.2.8}/jac_coder/runtime/skills.impl.jac +21 -0
  11. {jac_coder-0.2.7 → jac_coder-0.2.8}/jac_coder/runtime/skills.jac +3 -0
  12. jac_coder-0.2.8/jac_coder/runtime/struggle_report.jac +326 -0
  13. {jac_coder-0.2.7 → jac_coder-0.2.8}/jac_coder/server.jac +1 -1
  14. {jac_coder-0.2.7 → jac_coder-0.2.8}/jac_coder/tool/meta/delegation.impl.jac +21 -10
  15. {jac_coder-0.2.7 → jac_coder-0.2.8}/jac_coder/tool/meta/delegation.jac +1 -1
  16. {jac_coder-0.2.7 → jac_coder-0.2.8}/jac_coder/tool/net/preview.impl.jac +12 -2
  17. {jac_coder-0.2.7 → jac_coder-0.2.8}/jac_coder/tool/net/preview.jac +1 -1
  18. {jac_coder-0.2.7 → jac_coder-0.2.8}/jac_coder.egg-info/PKG-INFO +1 -2
  19. {jac_coder-0.2.7 → jac_coder-0.2.8}/jac_coder.egg-info/SOURCES.txt +1 -0
  20. {jac_coder-0.2.7 → jac_coder-0.2.8}/jac_coder.egg-info/requires.txt +0 -1
  21. {jac_coder-0.2.7 → jac_coder-0.2.8}/pyproject.toml +1 -2
  22. {jac_coder-0.2.7 → jac_coder-0.2.8}/README.md +0 -0
  23. {jac_coder-0.2.7 → jac_coder-0.2.8}/jac_coder/__init__.jac +0 -0
  24. {jac_coder-0.2.7 → jac_coder-0.2.8}/jac_coder/__init__.py +0 -0
  25. {jac_coder-0.2.7 → jac_coder-0.2.8}/jac_coder/api.jac +0 -0
  26. {jac_coder-0.2.7 → jac_coder-0.2.8}/jac_coder/cli.impl.jac +0 -0
  27. {jac_coder-0.2.7 → jac_coder-0.2.8}/jac_coder/cli.jac +0 -0
  28. {jac_coder-0.2.7 → jac_coder-0.2.8}/jac_coder/cli_entry.py +0 -0
  29. {jac_coder-0.2.7 → jac_coder-0.2.8}/jac_coder/core/__init__.jac +0 -0
  30. {jac_coder-0.2.7 → jac_coder-0.2.8}/jac_coder/infra/__init__.jac +0 -0
  31. {jac_coder-0.2.7 → jac_coder-0.2.8}/jac_coder/infra/config.impl.jac +0 -0
  32. {jac_coder-0.2.7 → jac_coder-0.2.8}/jac_coder/infra/events.jac +0 -0
  33. {jac_coder-0.2.7 → jac_coder-0.2.8}/jac_coder/infra/kv.jac +0 -0
  34. {jac_coder-0.2.7 → jac_coder-0.2.8}/jac_coder/infra/mcp_manager.impl.jac +0 -0
  35. {jac_coder-0.2.7 → jac_coder-0.2.8}/jac_coder/infra/mcp_manager.jac +0 -0
  36. {jac_coder-0.2.7 → jac_coder-0.2.8}/jac_coder/lib/__init__.jac +0 -0
  37. {jac_coder-0.2.7 → jac_coder-0.2.8}/jac_coder/lib/coder.impl.jac +0 -0
  38. {jac_coder-0.2.7 → jac_coder-0.2.8}/jac_coder/lib/coder.jac +0 -0
  39. {jac_coder-0.2.7 → jac_coder-0.2.8}/jac_coder/runtime/__init__.jac +0 -0
  40. {jac_coder-0.2.7 → jac_coder-0.2.8}/jac_coder/runtime/compaction.jac +0 -0
  41. {jac_coder-0.2.7 → jac_coder-0.2.8}/jac_coder/runtime/context.impl.jac +0 -0
  42. {jac_coder-0.2.7 → jac_coder-0.2.8}/jac_coder/runtime/context.jac +0 -0
  43. {jac_coder-0.2.7 → jac_coder-0.2.8}/jac_coder/runtime/cost_tracker.impl.jac +0 -0
  44. {jac_coder-0.2.7 → jac_coder-0.2.8}/jac_coder/runtime/cost_tracker.jac +0 -0
  45. {jac_coder-0.2.7 → jac_coder-0.2.8}/jac_coder/runtime/events.jac +0 -0
  46. {jac_coder-0.2.7 → jac_coder-0.2.8}/jac_coder/runtime/file_logger.jac +0 -0
  47. {jac_coder-0.2.7 → jac_coder-0.2.8}/jac_coder/runtime/memory.impl.jac +0 -0
  48. {jac_coder-0.2.7 → jac_coder-0.2.8}/jac_coder/runtime/memory.jac +0 -0
  49. {jac_coder-0.2.7 → jac_coder-0.2.8}/jac_coder/runtime/permission.impl.jac +0 -0
  50. {jac_coder-0.2.7 → jac_coder-0.2.8}/jac_coder/runtime/permission.jac +0 -0
  51. {jac_coder-0.2.7 → jac_coder-0.2.8}/jac_coder/serve_entry.jac +0 -0
  52. {jac_coder-0.2.7 → jac_coder-0.2.8}/jac_coder/tool/__init__.jac +0 -0
  53. {jac_coder-0.2.7 → jac_coder-0.2.8}/jac_coder/tool/git.impl.jac +0 -0
  54. {jac_coder-0.2.7 → jac_coder-0.2.8}/jac_coder/tool/git.jac +0 -0
  55. {jac_coder-0.2.7 → jac_coder-0.2.8}/jac_coder/tool/mcp.impl.jac +0 -0
  56. {jac_coder-0.2.7 → jac_coder-0.2.8}/jac_coder/tool/mcp.jac +0 -0
  57. {jac_coder-0.2.7 → jac_coder-0.2.8}/jac_coder/tool/meta/__init__.jac +0 -0
  58. {jac_coder-0.2.7 → jac_coder-0.2.8}/jac_coder/tool/meta/clarify_intent.impl.jac +0 -0
  59. {jac_coder-0.2.7 → jac_coder-0.2.8}/jac_coder/tool/meta/clarify_intent.jac +0 -0
  60. {jac_coder-0.2.7 → jac_coder-0.2.8}/jac_coder/tool/meta/task.impl.jac +0 -0
  61. {jac_coder-0.2.7 → jac_coder-0.2.8}/jac_coder/tool/meta/task.jac +0 -0
  62. {jac_coder-0.2.7 → jac_coder-0.2.8}/jac_coder/tool/meta/think.impl.jac +0 -0
  63. {jac_coder-0.2.7 → jac_coder-0.2.8}/jac_coder/tool/meta/think.jac +0 -0
  64. {jac_coder-0.2.7 → jac_coder-0.2.8}/jac_coder/tool/meta/todo.impl.jac +0 -0
  65. {jac_coder-0.2.7 → jac_coder-0.2.8}/jac_coder/tool/meta/todo.jac +0 -0
  66. {jac_coder-0.2.7 → jac_coder-0.2.8}/jac_coder/tool/meta/validate.impl.jac +0 -0
  67. {jac_coder-0.2.7 → jac_coder-0.2.8}/jac_coder/tool/meta/validate.jac +0 -0
  68. {jac_coder-0.2.7 → jac_coder-0.2.8}/jac_coder/tool/net/__init__.jac +0 -0
  69. {jac_coder-0.2.7 → jac_coder-0.2.8}/jac_coder/tool/net/web.impl.jac +0 -0
  70. {jac_coder-0.2.7 → jac_coder-0.2.8}/jac_coder/tool/net/web.jac +0 -0
  71. {jac_coder-0.2.7 → jac_coder-0.2.8}/jac_coder/tool/read/__init__.jac +0 -0
  72. {jac_coder-0.2.7 → jac_coder-0.2.8}/jac_coder/tool/read/filesystem.impl.jac +0 -0
  73. {jac_coder-0.2.7 → jac_coder-0.2.8}/jac_coder/tool/read/filesystem.jac +0 -0
  74. {jac_coder-0.2.7 → jac_coder-0.2.8}/jac_coder/tool/read/jac_analyzer.impl.jac +0 -0
  75. {jac_coder-0.2.7 → jac_coder-0.2.8}/jac_coder/tool/read/jac_analyzer.jac +0 -0
  76. {jac_coder-0.2.7 → jac_coder-0.2.8}/jac_coder/tool/read/load_jac_skill.impl.jac +0 -0
  77. {jac_coder-0.2.7 → jac_coder-0.2.8}/jac_coder/tool/read/load_jac_skill.jac +0 -0
  78. {jac_coder-0.2.7 → jac_coder-0.2.8}/jac_coder/tool/read/search.impl.jac +0 -0
  79. {jac_coder-0.2.7 → jac_coder-0.2.8}/jac_coder/tool/read/search.jac +0 -0
  80. {jac_coder-0.2.7 → jac_coder-0.2.8}/jac_coder/tool/run/__init__.jac +0 -0
  81. {jac_coder-0.2.7 → jac_coder-0.2.8}/jac_coder/tool/run/guarded.impl.jac +0 -0
  82. {jac_coder-0.2.7 → jac_coder-0.2.8}/jac_coder/tool/run/guarded.jac +0 -0
  83. {jac_coder-0.2.7 → jac_coder-0.2.8}/jac_coder/tool/run/jac_tools.impl.jac +0 -0
  84. {jac_coder-0.2.7 → jac_coder-0.2.8}/jac_coder/tool/run/jac_tools.jac +0 -0
  85. {jac_coder-0.2.7 → jac_coder-0.2.8}/jac_coder/tool/run/shell.impl.jac +0 -0
  86. {jac_coder-0.2.7 → jac_coder-0.2.8}/jac_coder/tool/run/shell.jac +0 -0
  87. {jac_coder-0.2.7 → jac_coder-0.2.8}/jac_coder/tool/write/__init__.jac +0 -0
  88. {jac_coder-0.2.7 → jac_coder-0.2.8}/jac_coder/tool/write/checked.impl.jac +0 -0
  89. {jac_coder-0.2.7 → jac_coder-0.2.8}/jac_coder/tool/write/checked.jac +0 -0
  90. {jac_coder-0.2.7 → jac_coder-0.2.8}/jac_coder/util/__init__.jac +0 -0
  91. {jac_coder-0.2.7 → jac_coder-0.2.8}/jac_coder/util/banner.jac +0 -0
  92. {jac_coder-0.2.7 → jac_coder-0.2.8}/jac_coder/util/colors.jac +0 -0
  93. {jac_coder-0.2.7 → jac_coder-0.2.8}/jac_coder/util/md_render.jac +0 -0
  94. {jac_coder-0.2.7 → jac_coder-0.2.8}/jac_coder/util/sandbox.impl.jac +0 -0
  95. {jac_coder-0.2.7 → jac_coder-0.2.8}/jac_coder/util/sandbox.jac +0 -0
  96. {jac_coder-0.2.7 → jac_coder-0.2.8}/jac_coder/util/tool_output.impl.jac +0 -0
  97. {jac_coder-0.2.7 → jac_coder-0.2.8}/jac_coder/util/tool_output.jac +0 -0
  98. {jac_coder-0.2.7 → jac_coder-0.2.8}/jac_coder.egg-info/dependency_links.txt +0 -0
  99. {jac_coder-0.2.7 → jac_coder-0.2.8}/jac_coder.egg-info/entry_points.txt +0 -0
  100. {jac_coder-0.2.7 → jac_coder-0.2.8}/jac_coder.egg-info/top_level.txt +0 -0
  101. {jac_coder-0.2.7 → jac_coder-0.2.8}/setup.cfg +0 -0
@@ -1,11 +1,10 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: jac-coder
3
- Version: 0.2.7
3
+ Version: 0.2.8
4
4
  Summary: AI coding agent backend for Jac, powered by jac-byllm
5
5
  Requires-Python: >=3.12
6
6
  Requires-Dist: python-dotenv>=1.0.0
7
7
  Requires-Dist: jaclang
8
- Requires-Dist: byllm
9
8
  Requires-Dist: mcp>=1.0.0
10
9
  Requires-Dist: jac-mcp
11
10
  Requires-Dist: rich>=13.0
@@ -339,7 +339,7 @@ impl chat(
339
339
  user_image = None;
340
340
  if images and len(images) > 0 {
341
341
  try {
342
- import from byllm.lib { Image }
342
+ import from jaclang.byllm.lib { Image }
343
343
  ;
344
344
  first_img = images[0];
345
345
  data_url = first_img.get("data_url", "") if isinstance(first_img, dict) else str(first_img);
@@ -358,6 +358,7 @@ impl chat(
358
358
 
359
359
  response_text = stream_result["content"];
360
360
  tools_used = stream_result["tools_used"];
361
+ tool_records = stream_result.get("tool_records", []);
361
362
 
362
363
  # Finalize turn — get accumulated file/error data from tool internals
363
364
  tool_end();
@@ -421,6 +422,14 @@ impl chat(
421
422
  import from jac_coder.runtime.prompt { infer_mode_hint as _infer_hint }
422
423
  session.last_mode_hint = _infer_hint(tools_used, files_modified);
423
424
 
425
+ # Guaranteed end-of-turn struggle reflection (work turns only).
426
+ if not is_abort_requested() {
427
+ import from jac_coder.runtime.struggle_report { reflect_and_log }
428
+ reflect_and_log(
429
+ main_agent, tool_records, files_modified, has_errors, response_text
430
+ );
431
+ }
432
+
424
433
  return {
425
434
  "response": response_text,
426
435
  "agent": "main",
@@ -552,8 +561,8 @@ impl api_check_local_setup(alias: str) -> dict {
552
561
  model: bool = False;
553
562
  valid_alias: bool = False;
554
563
  try {
555
- import from byllm.local_runtime { LOCAL_MODELS }
556
- import from byllm.model_cache { is_downloaded }
564
+ import from jaclang.byllm.local_runtime { LOCAL_MODELS }
565
+ import from jaclang.byllm.model_cache { is_downloaded }
557
566
  spec = LOCAL_MODELS.get(alias);
558
567
  if spec is not None {
559
568
  valid_alias = True;
@@ -87,10 +87,14 @@ sem MainAgent.respond = "Respond to the user's coding request.";
87
87
  # ---------------------------------------------------------------------------
88
88
  # SubAgent walkers — sem annotations and entry ability implementations
89
89
  # ---------------------------------------------------------------------------
90
- sem WorkerAgent.do_work = "Execute the delegated coding task.";
90
+ sem WorkerAgent.do_work = "Execute the delegated coding task as a pure implementer. When .jaccoder/ui_spec.json exists, treat it as a binding UI contract — never change style, theme, base_color, nav_pattern, or pages (those belong to UIDesignerAgent). If a mechanical implementation need arises for a component not in primitive_components (e.g. a spinner), add it to primitive_components in ui_spec.json first, then run jac add --shadcn. Never make freestyle design decisions.";
91
91
 
92
92
  sem ExplorerAgent.do_work = "Investigate the codebase and report findings.";
93
93
 
94
+ sem UIDesignerAgent = "UI design authority — produces or updates .jaccoder/ui_spec.json, the living design contract for the application.";
95
+ sem UIDesignerAgent.do_work = "Write or update .jaccoder/ui_spec.json. Create mode (no UISpec in task): analyse project from scratch and produce a full spec. Update mode (current UISpec JSON provided in task): apply the user's change request to only the affected fields, preserve everything else.";
96
+ sem UIDesignerAgent.task = "Input context in one of two forms. Create: project path + directory listing + jac.toml + user request. Update: project path + current UISpec JSON + user request.";
97
+
94
98
 
95
99
  """Consume a SubAgent's by llm() stream, forward events, and return results."""
96
100
  def _consume_subagent_stream(event_stream: Any, prefix: str, track_files: bool = False) -> dict {
@@ -134,6 +138,11 @@ def _consume_subagent_stream(event_stream: Any, prefix: str, track_files: bool =
134
138
  }
135
139
  } elif event.event_type == "steps_done" {
136
140
  emit_event("llm_steps_done", dict(event.data));
141
+ } elif event.event_type == "stream_reset" {
142
+ # byllm retried a transient mid-stream drop; drop the partial subagent
143
+ # reply so the regenerated text isn't doubled.
144
+ content = "";
145
+ emit_event("llm_stream_reset", dict(event.data));
137
146
  } elif event.event_type == "chunk" {
138
147
  content += event.data.get("content", "");
139
148
  } elif event.event_type == "usage" {
@@ -169,3 +178,11 @@ impl ExplorerAgent.execute with MainAgent entry {
169
178
  self.result_files = [];
170
179
  self.result_errors = list(result["errors"]);
171
180
  }
181
+
182
+
183
+ impl UIDesignerAgent.execute with MainAgent entry {
184
+ result = _consume_subagent_stream(
185
+ self.do_work(task_str=self.task), "sub:designer"
186
+ );
187
+ self.result_content = str(result["content"]);
188
+ }
@@ -3,7 +3,7 @@ import sys;
3
3
 
4
4
  import from uuid { uuid4 }
5
5
  import from datetime { datetime }
6
- import from byllm.types { IterationAction, IterationContext }
6
+ import from jaclang.byllm.types { IterationAction, IterationContext }
7
7
  import from jac_coder.infra.config { llm, get_config }
8
8
  import from jac_coder.runtime.events { is_abort_requested }
9
9
 
@@ -24,8 +24,8 @@ import from jac_coder.tool.read.jac_analyzer { analyze_project, find_symbol }
24
24
  import from jac_coder.tool.net.preview { browser_open, browser_do, browser_state, browser_close, browser_validate }
25
25
  import from jac_coder.tool.mcp { mcp_call }
26
26
  import from jac_coder.runtime.compaction { jaccoder_compaction }
27
- import from jac_coder.runtime.prompt { compose_system_prompt }
28
- import from byllm.lib { Image }
27
+ import from jac_coder.runtime.prompt { compose_system_prompt, compose_system_prompt_filtered }
28
+ import from jaclang.byllm.lib { Image }
29
29
 
30
30
 
31
31
 
@@ -64,6 +64,43 @@ sem SubAgentResult.tools_used = "Tools called during execution";
64
64
  sem SubAgentResult.iterations_used = "Number of ReAct iterations consumed";
65
65
 
66
66
 
67
+ # Controlled vocabulary for the struggle's root cause. A fixed enum (not a free
68
+ # string) so categories stay consistent across users and the admin portal can
69
+ # group/triage them. Each value maps to an improvement action: skills_gap -> write
70
+ # a skill doc, upstream_bug -> file a Jaseci issue, environment_issue -> fix the
71
+ # sandbox, etc.
72
+ enum StruggleCategory {
73
+ SKILLS_GAP = "skills_gap",
74
+ UPSTREAM_BUG = "upstream_bug",
75
+ SYNTAX_ERROR = "syntax_error",
76
+ FRAMEWORK_API = "framework_api",
77
+ ENVIRONMENT_ISSUE = "environment_issue",
78
+ TOOLING_LIMITATION = "tooling_limitation",
79
+ LOGIC_ERROR = "logic_error",
80
+ AMBIGUITY = "ambiguity",
81
+ NONE = "none"
82
+ }
83
+
84
+
85
+ obj TurnReflection {
86
+ has issue: str = "",
87
+ root_cause: str = "",
88
+ resolution: str = "",
89
+ code_before: str = "",
90
+ code_after: str = "",
91
+ smoothness: float = 0.7,
92
+ category: StruggleCategory = StruggleCategory.NONE;
93
+ }
94
+
95
+ sem TurnReflection.issue = "The single biggest sticking point this turn and WHERE it was (file / function / component). 1-2 sentences.";
96
+ sem TurnReflection.root_cause = "The concrete underlying cause: the exact error, the API you misunderstood, the wrong assumption. Be specific, not generic.";
97
+ sem TurnReflection.resolution = "How you fixed it, in one sentence, or 'unresolved' if you did not fix it.";
98
+ sem TurnReflection.code_before = "The minimal broken code (just the few key lines), if it was a code problem. Empty otherwise.";
99
+ sem TurnReflection.code_after = "Those same lines, fixed. Empty otherwise.";
100
+ sem TurnReflection.smoothness = "0.0 (fought it for many iterations or left it unresolved) to 1.0 (effortless). Ground it in the effort the digest shows - a long unresolved struggle is LOW.";
101
+ sem TurnReflection.category = "Pick the ONE category that best fits the struggle's root cause. skills_gap: you lacked the right Jac pattern and the skills/docs did not cover it. upstream_bug: a real bug or limitation in Jac/Jaseci/jac-cloud itself, not your own mistake. syntax_error: Jac or cl.jac syntax you got wrong. framework_api: you misunderstood a Jac/jac-cloud API or concept (persistence, walkers, nodes/edges, routing, auth). environment_issue: sandbox, deployment, install, or server/tooling environment problem. tooling_limitation: a JacCoder tool itself fell short (edit match failed, browser, etc.). logic_error: your own application logic/reasoning bug. ambiguity: the user request was unclear or underspecified. none: no significant struggle this turn.";
102
+
103
+
67
104
  obj ProjectScan {
68
105
  has architecture: str;
69
106
  has file_map: dict[str, str];
@@ -271,6 +308,29 @@ node MainAgent {
271
308
  stream=True,
272
309
  logging=True
273
310
  );
311
+
312
+ """You ARE the coding agent, reflecting on the turn you just finished, for an
313
+ internal quality log the user never sees. The digest below is the turn: your own
314
+ think reasoning, every tool call (failures and functional/app problems are
315
+ flagged), the code you changed, and the cost (how many calls).
316
+
317
+ Find the SINGLE most significant struggle - the one that cost the most effort or
318
+ stayed unresolved, NOT the tidiest one-liner. A functional bug you debugged over
319
+ many iterations (e.g. the app showing wrong data, a "not found" that kept failing)
320
+ matters far more than a quick procedural realization. Weight by effort and whether
321
+ it was resolved, not by how cleanly you can state it.
322
+
323
+ Then report it concretely: `issue` (what + WHERE), `root_cause` (the real
324
+ underlying cause), `resolution` (how you fixed it, or "unresolved"), and if it was
325
+ a code problem, a MINIMAL `code_before`/`code_after` (just the few key lines).
326
+ Set `smoothness` from the effort the digest shows: a long or unresolved struggle
327
+ is LOW (0.2-0.4), a smooth turn is high. Choose the `category` that best matches
328
+ the root cause from the allowed values. If the turn
329
+ really was smooth, say so briefly with high smoothness and empty code fields."""
330
+ def reflect_on_turn(turn_digest: str) -> TurnReflection by llm(
331
+ temperature=0.3,
332
+ max_tokens=700
333
+ );
274
334
  }
275
335
 
276
336
 
@@ -288,6 +348,84 @@ def _iteration_hook(ctx: IterationContext) -> IterationAction {
288
348
  }
289
349
 
290
350
 
351
+ glob DESIGNER_SKILLS: set[str] = {"jac-shadcn-components", "jac-shadcn-blocks"};
352
+
353
+
354
+ glob JACCODER_DESIGNER_SYSTEM: str = """
355
+ # UIDesignerAgent — UI Design Authority
356
+
357
+ Produce or update `.jaccoder/ui_spec.json` — the UI design contract. You do NOT write application code, run commands, or build anything.
358
+
359
+ ## Two operating modes
360
+
361
+ ### Create mode (no UISpec in task)
362
+ Task: project path + directory listing + jac.toml + user request. Analyse from scratch and produce a complete UISpec.
363
+ 1. Load `jac-shadcn-blocks` skill — `### Section headers` give valid block names.
364
+ 2. Load `jac-shadcn-components` skill — Component Selection table gives primitive names; § Theming gives valid theming values.
365
+ 3. Write the full JSON to `<project_dir>/.jaccoder/ui_spec.json` using `write_code`.
366
+
367
+ ### Update mode (current UISpec provided in task)
368
+ Task: project path + current UISpec JSON + user request. Update ONLY the relevant fields — preserve everything else.
369
+ 1. Load `jac-shadcn-blocks` (if change involves pages or blocks).
370
+ 2. Load `jac-shadcn-components` (if change involves theme, style, font, or primitive_components).
371
+ 3. Apply only the requested changes. Write the complete JSON back (full file, not a patch).
372
+
373
+ Changes → fields: visual redesign ("more attractive", "production-level", "like Linear/Notion") → `style/theme/base_color/font/radius/menu_accent`; nav change → `nav_pattern` + page `blocks`; add/remove page → `pages[]` + `primitive_components`; component addition → `primitive_components`.
374
+
375
+ ## Output — `.jaccoder/ui_spec.json`
376
+ Single raw JSON object (no markdown fences, no comments). All fields required.
377
+
378
+ ```
379
+ {
380
+ "app_type": "landing | dashboard | saas | ecommerce | blog | portfolio | tool",
381
+ "auth_required": true | false,
382
+ "backend_required": true | false,
383
+ "nav_pattern": "sidebar | topnav | bottomnav | none",
384
+ "pages": [
385
+ {
386
+ "name": "Human-readable page name",
387
+ "route": "/path",
388
+ "blocks": ["block_name_1"],
389
+ "auth_protected": true | false,
390
+ "title": "HTML page title"
391
+ }
392
+ ],
393
+ "primitive_components": ["button", "card"],
394
+ "style": "nova | vega | maia | lyra | mira",
395
+ "base_color": "neutral | stone | zinc | gray",
396
+ "theme": "rose | indigo | emerald | amber | blue | violet | teal | green",
397
+ "font": "figtree | inter | geist | dm-sans | roboto",
398
+ "radius": "default | none | small | medium | large",
399
+ "menu_accent": "subtle | bold",
400
+ "confidence": 0.0,
401
+ "assumption": "One sentence describing the biggest assumption made.",
402
+ "remove_from_template": []
403
+ }
404
+ ```
405
+
406
+ Note: `theme` and `font` show the most common values — see jac-shadcn-components § Theme reference and § Font reference for the full lists with descriptions.
407
+
408
+ ## Field guidance
409
+
410
+ **app_type**: match to user's domain.
411
+ **auth_required**: DEFAULT is `false`. Set `true` ONLY if the user's message contains explicit words like "login", "signup", "sign in", "user accounts", "authentication", or "protected pages". Do NOT infer from app type — "habit tracker", "todo app", "notes app", "dashboard" do NOT imply auth. When in doubt → `false`.
412
+ **backend_required**: `true` if server endpoints, a database, or per-user data needed.
413
+ **pages[].blocks**: exact snake_case names from jac-shadcn-blocks skill only — auth_card, app_shell_sidebar, data_table, stats_row, pricing_grid, cta_banner, empty_state, marketing_section. Do not invent names.
414
+ **primitive_components**: exact lowercase names from jac-shadcn-components Component Selection table — passed to `jac add --shadcn` (e.g. "button", "card", "sidebar", "badge", "table", "input", "dialog", "select", "avatar", "chart"). No custom names like "HabitCard". List primaries only — peer deps auto-resolve.
415
+ **style**: visual language — `nova` (neutral professional B2B), `vega` (vibrant modern consumer), `maia` (warm approachable/health), `lyra` (data-focused minimal), `mira` (creative expressive). See jac-shadcn-components § Theming.
416
+ **base_color**: gray palette for backgrounds/borders/muted text. NOT a hex color. `neutral` (default/most apps), `stone` (warm/organic), `zinc` (cool/technical), `gray` (mid). See jac-shadcn-components § baseColor vs theme.
417
+ **theme**: accent/brand color name. NOT "light"/"dark"/"system". See jac-shadcn-components § Theme reference for full list (e.g. `indigo`=enterprise, `emerald`=health/wellness, `rose`=lifestyle, `amber`=e-commerce).
418
+ **font**: see jac-shadcn-components § Font reference for full list and tone descriptions. Default `figtree` (warm/friendly). `inter` for professional, `geist` for technical.
419
+ **radius**: `default` for most apps. `none`/`small` for enterprise/data. `medium`/`large` for consumer/mobile.
420
+ **menu_accent**: `subtle` (muted, low distraction — Notion/GitHub style). `bold` (primary-colored — Linear/Vercel style).
421
+ **remove_from_template**: only files confirmed present in directory listing. Set `[]` when uncertain.
422
+ **confidence**: 0.0-1.0. Create mode: how well the request maps to a complete spec. Update mode: how well the changes match the user's request.
423
+
424
+ ## After writing the file
425
+ Confirm the write succeeded. Your task is done — do not build, run commands, or write any application code.
426
+ """;
427
+
428
+
291
429
  glob JACCODER_WORKER_SYSTEM: str = """
292
430
  # WorkerAgent — Jac Coding SubAgent
293
431
 
@@ -302,20 +440,58 @@ Build breadth-first — get all files written and app running before polishing.
302
440
  ## UI (mandatory)
303
441
  Build responsive, mobile-first UIs: fluid flex/grid layouts, Tailwind responsive prefixes (sm:/md:/lg:), no fixed widths that overflow small screens. A desktop-only layout that breaks on mobile is a bug. After browser_validate PASS, also check the layout at a narrow/mobile width.
304
442
 
443
+ ## Pre-flight — PATH A setup (run BEFORE writing any code files)
444
+ If `.jaccoder/ui_spec.json` exists, these two commands MUST run before you write or edit any code file:
445
+
446
+ **1. Install primitives:**
447
+ `jac add --shadcn <primitive_components from UISpec>` — use the exact list, nothing more.
448
+
449
+ **2. Apply theme:**
450
+ ```
451
+ jac retheme --style <style> --baseColor <base_color> --theme <theme> --font <font> --radius <radius> --menuAccent <menu_accent>
452
+ ```
453
+ All 6 flags are required — never omit any, even when the value is the default (e.g. `--radius default`). UISpec uses snake_case; flags are camelCase (`base_color`→`--baseColor`, `menu_accent`→`--menuAccent`). This overwrites `global.css` wholesale.
454
+
455
+ Run both before touching services, components, or any other file.
456
+
305
457
  ## Workflow
306
458
  1. Read .jaccoder/progress.md if it exists, update it as you go.
307
459
  2. analyze_project(dir) for existing projects.
308
- 3. Build bottom-up: services → hooks → components → layout.
309
- 4. **If `components/ui/` exists AND jac.toml has `[jac-shadcn]` (jac-shadcn project):**
310
- - Load `jac-shadcn-components` skill BEFORE writing any UI.
311
- - Load `jac-cl-styling` for conditional class and cn() patterns.
312
- - `styles/global.css` and `jac.toml` are pre-configured — do NOT recreate or modify them.
313
- - Import UI from `components/ui/` instead of writing raw primitive components. No inline styles.
314
- **Otherwise (standard project):**
315
- - jac.toml needs `[plugins.client.vite]` with tailwindcss. Run `jac install` after toml changes.
316
- - styles/global.css: `@import "tailwindcss"`, @theme tokens. In main.jac: `cl import ".styles.global.css";`
317
- - Load `jac-cl-styling` when writing Tailwind classes or dynamic styles.
318
- - Load `jac-npm-packages` when adding third-party npm packages.
460
+ 3. For PATH B/C: build bottom-up services → hooks → components → layout. For PATH A, follow steps a–d below (page-first, not service-first).
461
+ 4. **Choose your build path:**
462
+
463
+ ### PATH A UISpec present (`.jaccoder/ui_spec.json` exists)
464
+ Pre-flight already done above. Then:
465
+
466
+ a) **Remove unwanted template files** listed in `remove_from_template` early, before writing new pages. Re-verify each file exists before deleting.
467
+
468
+ b) **For each page in `pages`**:
469
+ - Load `jac-shadcn-blocks` skill. Use the listed `blocks` in order to build the page.
470
+ - Load `jac-shadcn-components` for shared UI primitives.
471
+ - Register the route in the app router.
472
+
473
+ c) **Auth — detect and ensure** based on `auth_required`:
474
+ - If `true`:
475
+ - **Frontend check**: Does `pages/LoginPage.cl.jac` exist AND does `index.cl.jac` (or router file) contain `AuthGuard`?
476
+ - If MISSING: load `jac-shadcn-blocks` (use `auth_card` block) + `jac-cl-auth` skill. Build `pages/LoginPage.cl.jac` and wrap protected routes with `<AuthGuard redirect="/login">` in the router.
477
+ - If EXISTS: keep it.
478
+ - **Backend check**: Does `jac.toml` contain `[plugins.server]` OR does a `services/` dir exist?
479
+ - If MISSING: load `jac-sv-auth` + `jac-sv-endpoints` skills. Add `[plugins.server]` section to `jac.toml`. Create `services/appService.jac` with a minimal skeleton. Run `jac install`.
480
+ - If EXISTS: keep it.
481
+ - Note: jac-scale auto-provides `/user/login` and `/user/register` — do NOT write custom auth walkers.
482
+ - If `false`: delete `pages/LoginPage.cl.jac` if it exists; remove `AuthGuard` wrapper from router if present.
483
+
484
+ d) **Backend — detect and ensure** based on `backend_required` (independent of auth):
485
+ - If `true` and no backend present (same check as above): load `jac-sv-endpoints`, add `[plugins.server]` to `jac.toml`, create `services/appService.jac` skeleton, run `jac install`.
486
+ - If `false`: leave any existing backend untouched.
487
+
488
+
489
+ ### PATH B — jac-shadcn project, no UISpec
490
+ Check: `components/ui/` exists OR `jac.toml` has `[jac-shadcn]`. Load `jac-shadcn-components` + `jac-shadcn-blocks` skills and build based on the task description. Apply sensible design defaults. Do NOT create or modify `styles/global.css` or `jac.toml` (pre-configured by jac-shadcn).
491
+
492
+ ### PATH C — Standard project (no jac-shadcn)
493
+ Load `jac-cl-styling` and build with plain Tailwind. `jac.toml` needs `[plugins.client.vite]` with tailwindcss. Run `jac install` after toml changes. `styles/global.css`: `@import "tailwindcss"`. Load `jac-npm-packages` when adding third-party packages.
494
+
319
495
  5. After all files: jac start --dev main.jac (background=True).
320
496
  6. browser_validate(url) and follow its instructions.
321
497
  """;
@@ -405,3 +581,27 @@ walker ExplorerAgent {
405
581
  );
406
582
  can execute with MainAgent entry;
407
583
  }
584
+
585
+
586
+ walker UIDesignerAgent {
587
+ has task: str;
588
+ has result_content: str = "";
589
+
590
+ def do_work(task_str: str) -> str by llm(
591
+ system_prompt=compose_system_prompt_filtered(JACCODER_DESIGNER_SYSTEM, DESIGNER_SKILLS),
592
+ tools=[
593
+ think,
594
+ load_jac_skill,
595
+ list_files,
596
+ read_file,
597
+ write_code
598
+ ],
599
+ on_iteration=_iteration_hook,
600
+ max_react_iterations=15,
601
+ temperature=0.1,
602
+ max_tokens=8192,
603
+ stream=True,
604
+ logging=True
605
+ );
606
+ can execute with MainAgent entry;
607
+ }
@@ -4,6 +4,7 @@
4
4
 
5
5
  glob _PROGRESS_DIR: str = ".jaccoder";
6
6
  glob _PROGRESS_FILE: str = "progress.md";
7
+ glob _UI_SPEC_FILE: str = "ui_spec.json";
7
8
 
8
9
 
9
10
  """Read .jaccoder/progress.md from the project directory. Returns content or empty string."""
@@ -30,6 +31,20 @@ def _read_progress(project_dir: str) -> str {
30
31
  }
31
32
 
32
33
 
34
+ def _read_ui_spec(project_dir: str) -> str {
35
+ spec_path = os.path.join(project_dir, _PROGRESS_DIR, _UI_SPEC_FILE);
36
+ if not os.path.isfile(spec_path) {
37
+ return "";
38
+ }
39
+ try {
40
+ with open(spec_path, "r", encoding="utf-8") as f {
41
+ return f.read();
42
+ }
43
+ } except Exception {
44
+ return "";
45
+ }
46
+ }
47
+
33
48
  impl ensure_main_agent() -> MainAgent {
34
49
  agents = [root()-->][?:MainAgent];
35
50
  if agents {
@@ -256,6 +271,15 @@ impl _consume_llm_stream(event_stream: Any) -> dict {
256
271
  emit_event("llm_tool_result", dict(event.data));
257
272
  } elif event.event_type == "steps_done" {
258
273
  emit_event("llm_steps_done", dict(event.data));
274
+ } elif event.event_type == "stream_reset" {
275
+ # byllm retried a transient drop: discard the partial reply (so the
276
+ # regenerated text isn't doubled) and forward the signal to the UI.
277
+ if event.data.get("scope") == "answer" {
278
+ response_text = "";
279
+ } else {
280
+ thought_text = "";
281
+ }
282
+ emit_event("llm_stream_reset", dict(event.data));
259
283
  } elif event.event_type == "chunk" {
260
284
  chunk_content = event.data.get("content", "");
261
285
  response_text += chunk_content;
@@ -319,6 +343,19 @@ impl _respond_and_persist(agent: MainAgent, ctx: interact) -> None {
319
343
  );
320
344
  }
321
345
 
346
+ if work_dir {
347
+ ui_spec_json = _read_ui_spec(work_dir);
348
+ if ui_spec_json {
349
+ prefix_parts.append(
350
+ "<ui-spec>\n"
351
+ "UISpec produced by UIDesignerAgent. "
352
+ "WorkerAgent MUST follow it when building or modifying pages.\n\n"
353
+ + ui_spec_json
354
+ + "\n</ui-spec>"
355
+ );
356
+ }
357
+ }
358
+
322
359
  if project_summary {
323
360
  prefix_parts.append(
324
361
  "[INTERNAL — background context from .jaccoder/progress.md. "
@@ -431,6 +468,12 @@ impl _respond_and_persist(agent: MainAgent, ctx: interact) -> None {
431
468
  emit_previewable();
432
469
  }
433
470
 
471
+ # Guaranteed end-of-turn struggle reflection (work turns only).
472
+ if not is_abort_requested() {
473
+ import from jac_coder.runtime.struggle_report { reflect_and_log }
474
+ reflect_and_log(agent, tool_records, files_modified, has_errors, response_text);
475
+ }
476
+
434
477
  report {
435
478
  "response": response_text,
436
479
  "agent": "main",
@@ -8,13 +8,13 @@ import from jac_coder.infra.config { llm, get_config }
8
8
  import from jaclang.runtimelib.builtin { restspec }
9
9
  import from jac_coder.runtime.context { build_context, ContextConfig }
10
10
  import from jac_coder.runtime.events { reset_steps, start_turn, emit_turn_summary, emit_event, clear_abort, is_abort_requested, emit_phase_change, emit_previewable }
11
- import from byllm.types { StreamEvent }
11
+ import from jaclang.byllm.types { StreamEvent }
12
12
  import from jac_coder.runtime.memory {
13
13
  find_or_create_memory,
14
14
  _init_memory,
15
15
  update_memory_from_session
16
16
  }
17
- import from jac_coder.core.nodes { Session, MainAgent }
17
+ import from jac_coder.core.nodes { Session, MainAgent, UIDesignerAgent }
18
18
 
19
19
 
20
20
  def ensure_main_agent() -> MainAgent;
@@ -4,7 +4,7 @@ import threading;
4
4
  import from contextlib { suppress }
5
5
  import from pathlib { Path }
6
6
  import from dotenv { load_dotenv }
7
- import from byllm.lib { Model }
7
+ import from jaclang.byllm.lib { Model }
8
8
 
9
9
 
10
10
  obj JacCoderConfig {
@@ -6,6 +6,15 @@ impl compose_system_prompt(base_prompt: str) -> str {
6
6
  return (prompt + "\n\n" + listing) if listing else prompt;
7
7
  }
8
8
 
9
+ impl compose_system_prompt_filtered(base_prompt: str, allowed_skills: set[str]) -> str {
10
+ skills_block = inject_skills_listing_filtered(allowed_skills);
11
+ return (base_prompt + "\n\n" + skills_block) if skills_block else base_prompt;
12
+ }
13
+
14
+ impl smells_like_build(message: str) -> bool {
15
+ return _smells_like_build(message);
16
+ }
17
+
9
18
  # ---------------------------------------------------------------------------
10
19
  # infer_mode_hint — classify what the agent did from ground truth
11
20
  # ---------------------------------------------------------------------------
@@ -11,7 +11,7 @@ Signal chain per turn:
11
11
  """
12
12
 
13
13
  import os;
14
- import from jac_coder.runtime.skills { inject_skills_listing }
14
+ import from jac_coder.runtime.skills { inject_skills_listing, inject_skills_listing_filtered }
15
15
 
16
16
  # ---------------------------------------------------------------------------
17
17
  # Mode hint constants — stored as strings for graph persistence safety
@@ -53,6 +53,17 @@ You have limited iterations. Prioritize a WORKING app over a perfect one:
53
53
 
54
54
  ## Build Workflow
55
55
  0. **Inspect the workspace** (per Step 0 above) — decide EXTEND vs CREATE before anything else.
56
+ 0.5. **UIDesignerAgent — UI design authority** (UIDesignerAgent owns ALL UI design decisions):
57
+
58
+ **Condition A — `.jaccoder/ui_spec.json` does NOT exist** (first build): Run UIDesignerAgent now, before any skill loading or file writing:
59
+ `spawn_agent(mode='designer', task="Design UISpec for <abs_path>\n\nDirectory listing:\n<paste list_files output>\n\nProject config (jac.toml):\n<paste jac.toml content, or 'not present'>\n\nUser request: <user_message>")`
60
+ Pass the directory listing and jac.toml you already read — UIDesignerAgent will use them directly. Wait for it to complete, then proceed to step 1.
61
+
62
+ **Condition B — `.jaccoder/ui_spec.json` EXISTS and the request involves UI design changes**: UI design changes include: visual redesign ("more attractive", "production-level", "like X"), theme/style/font changes, navigation structure changes, adding or removing pages, layout changes. Run UIDesignerAgent in update mode:
63
+ `spawn_agent(mode='designer', task="Update UISpec for <abs_path>\n\nCurrent UISpec:\n<paste .jaccoder/ui_spec.json content>\n\nUser request: <user_message>")`
64
+ Wait for UIDesignerAgent to update `ui_spec.json`, then spawn WorkerAgent to implement the changes.
65
+
66
+ **Condition C — `.jaccoder/ui_spec.json` EXISTS and request is feature or code only**: Bug fixes, logic changes, backend features, content changes. Skip UIDesignerAgent. Proceed to step 1 and spawn WorkerAgent.
56
67
  1. Load skills from `<jac-skills-available>` via `load_jac_skill(name)`: **always start with `jac-core-cheatsheet`**, then load the task-specific skills (fullstack + anything the task touches).
57
68
  2. Plan briefly, then create/update `.jaccoder/progress.md` with the plan. Keep planning SHORT: make each choice ONCE and move on — never re-litigate the same decision and don't restate the plan in chat. The plan lives in progress.md, not in long prose.
58
69
  3. Backend services (.sv.jac), then a minimal main.jac that renders a real layout (not a stub) — your live-preview shell; it grows as you wire in components.
@@ -197,3 +208,9 @@ No emojis in generated UI (headings, buttons, labels, empty states, toasts) or i
197
208
  """Build an agent's full system prompt: base persona + shared static context
198
209
  that belongs in the cached prompt prefix (currently the skills listing)."""
199
210
  def compose_system_prompt(base_prompt: str) -> str;
211
+
212
+ """Like compose_system_prompt but only includes skills listed in `allowed_skills`. Used by per-agent prompts."""
213
+ def compose_system_prompt_filtered(base_prompt: str, allowed_skills: set[str]) -> str;
214
+
215
+ """Public wrapper for the private _smells_like_build check."""
216
+ def smells_like_build(message: str) -> bool;
@@ -123,6 +123,27 @@ impl inject_skills_listing() -> str {
123
123
  }
124
124
 
125
125
 
126
+ impl inject_skills_listing_filtered(allowed: set[str]) -> str {
127
+ reg = get_default_registry();
128
+ lines: list[str] = [];
129
+ for name in allowed {
130
+ if name in reg.skills {
131
+ meta = reg.skills[name];
132
+ lines.append(f"- {meta.name}: {meta.description}");
133
+ }
134
+ }
135
+ if not lines {
136
+ return "";
137
+ }
138
+ body = "\n".join(lines);
139
+ return (
140
+ "<jac-skills-available>\n"
141
+ + body
142
+ + "\n</jac-skills-available>"
143
+ );
144
+ }
145
+
146
+
126
147
  # ---------------------------------------------------------------------------
127
148
  # Private helpers
128
149
  # ---------------------------------------------------------------------------
@@ -89,3 +89,6 @@ def get_default_registry() -> SkillRegistry;
89
89
  """Render the available-skills listing as a single system-message string.
90
90
  Returns empty string when no skills are installed."""
91
91
  def inject_skills_listing() -> str;
92
+
93
+ """Render only skills whose names are in `allowed`. No cache — used for per-agent filtered prompts."""
94
+ def inject_skills_listing_filtered(allowed: set[str]) -> str;