code-context-control 2.43.0__py3-none-any.whl → 2.44.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 (42) hide show
  1. cli/c3.py +137 -1
  2. cli/commands/parser.py +27 -0
  3. cli/hub_server.py +598 -1
  4. cli/hub_ui/app.js +131 -0
  5. cli/hub_ui/components/add_project.js +79 -0
  6. cli/hub_ui/components/config_editor.js +233 -0
  7. cli/hub_ui/components/drill_health.js +297 -0
  8. cli/hub_ui/components/drill_panel.js +166 -0
  9. cli/hub_ui/components/drill_views.js +333 -0
  10. cli/hub_ui/components/global_search.js +184 -0
  11. cli/hub_ui/components/mcp_manager.js +379 -0
  12. cli/hub_ui/components/modals.js +619 -0
  13. cli/hub_ui/components/project_card.js +408 -0
  14. cli/hub_ui/components/project_tree.js +103 -0
  15. cli/hub_ui/components/session_drawer.js +178 -0
  16. cli/hub_ui/components/settings_modal.js +210 -0
  17. cli/hub_ui/components/sidebar.js +106 -0
  18. cli/hub_ui/components/summary_bar.js +71 -0
  19. cli/hub_ui/components/toasts.js +70 -0
  20. cli/hub_ui/components/topbar.js +64 -0
  21. cli/hub_ui/state.js +147 -0
  22. cli/hub_ui.html +138 -0
  23. cli/mcp_server.py +10 -5
  24. cli/tools/federate.py +121 -0
  25. cli/tools/memory.py +22 -3
  26. cli/tools/project.py +103 -2
  27. cli/tools/search.py +16 -1
  28. cli/ui/icons.js +11 -0
  29. {code_context_control-2.43.0.dist-info → code_context_control-2.44.0.dist-info}/METADATA +13 -6
  30. {code_context_control-2.43.0.dist-info → code_context_control-2.44.0.dist-info}/RECORD +42 -21
  31. core/config.py +7 -0
  32. services/doc_index.py +12 -0
  33. services/indexer.py +18 -0
  34. services/project_manager.py +29 -1
  35. services/project_runtime.py +6 -1
  36. services/protocol.py +8 -0
  37. services/subprojects.py +591 -0
  38. services/watcher.py +10 -2
  39. {code_context_control-2.43.0.dist-info → code_context_control-2.44.0.dist-info}/WHEEL +0 -0
  40. {code_context_control-2.43.0.dist-info → code_context_control-2.44.0.dist-info}/entry_points.txt +0 -0
  41. {code_context_control-2.43.0.dist-info → code_context_control-2.44.0.dist-info}/licenses/LICENSE +0 -0
  42. {code_context_control-2.43.0.dist-info → code_context_control-2.44.0.dist-info}/top_level.txt +0 -0
cli/c3.py CHANGED
@@ -85,7 +85,7 @@ console = Console() if HAS_RICH else None
85
85
  # Config
86
86
  CONFIG_DIR = ".c3"
87
87
  CONFIG_FILE = ".c3/config.json"
88
- __version__ = "2.43.0"
88
+ __version__ = "2.44.0"
89
89
 
90
90
 
91
91
  def _command_deps() -> CommandDeps:
@@ -1051,6 +1051,10 @@ def cmd_init(args):
1051
1051
  # ── Non-interactive (--clear) ──────────────────────────────
1052
1052
  if getattr(args, "clear", False):
1053
1053
  print("\n[--clear] Wiping C3 files...")
1054
+ parent_link = (load_config(project_path) or {}).get("parent") or {}
1055
+ if parent_link.get("path"):
1056
+ print(f" [!] This project is a sub-project of {parent_link['path']}")
1057
+ print(" The parent still lists it -- run 'c3 sub check --fix' there.")
1054
1058
  _uninstall_mcp_all(project_path)
1055
1059
  if c3_dir.exists():
1056
1060
  shutil.rmtree(c3_dir)
@@ -5733,6 +5737,137 @@ def cmd_projects(args):
5733
5737
  print(f" Port {s['port']:>5} {s.get('project_name', '?'):<25} {s.get('project_path', '')}")
5734
5738
 
5735
5739
 
5740
+ def cmd_sub(args):
5741
+ """Manage sub-projects: designated sub-folders with linked .c3 branches."""
5742
+ parent = str(Path(getattr(args, "parent", ".") or ".").resolve())
5743
+ if not (Path(parent) / CONFIG_DIR).is_dir():
5744
+ print(f"No .c3 found in {parent}. Run 'c3 init' there first.")
5745
+ return
5746
+ # Import after the cheap guard — the registry module needs a resolvable home.
5747
+ from services.subprojects import VALID_CASCADE_OPS, SubprojectManager
5748
+
5749
+ sm = SubprojectManager(parent)
5750
+ sub = getattr(args, "sub_cmd", "list") or "list"
5751
+ target = getattr(args, "target", None)
5752
+ as_json = getattr(args, "json", False)
5753
+
5754
+ if sub == "add":
5755
+ if not target:
5756
+ print("Usage: c3 sub add <folder> [--parent PATH] [--name NAME]")
5757
+ return
5758
+ result = sm.add(
5759
+ target,
5760
+ name=getattr(args, "name", None),
5761
+ ide=getattr(args, "ide", None),
5762
+ run_init=not getattr(args, "no_init", False),
5763
+ reindex_parent=not getattr(args, "no_reindex_parent", False),
5764
+ )
5765
+ if as_json:
5766
+ print(json.dumps(result, indent=2))
5767
+ return
5768
+ if not result.get("added"):
5769
+ print(f"Failed: {result.get('error')}")
5770
+ return
5771
+ verb = "Adopted (existing .c3 kept)" if result.get("adopted") else "Initialized"
5772
+ print(f"\n[OK] {verb}: {result['name']} ({result['path']})")
5773
+ code = (result.get("parent_reindex") or {}).get("code")
5774
+ if code:
5775
+ print(f" Parent reindexed: {code.get('files_indexed', '?')} files, "
5776
+ f"{code.get('chunks_created', '?')} chunks (sub-project now excluded)")
5777
+
5778
+ elif sub == "list":
5779
+ report = sm.reconcile(fix=False) # report-only consistency pass
5780
+ children = sm.list()
5781
+ if as_json:
5782
+ print(json.dumps({"children": children, "orphans": report.get("orphans", [])}, indent=2))
5783
+ return
5784
+ if not children:
5785
+ print("No sub-projects designated. Use `c3 sub add <folder>`.")
5786
+ return
5787
+ fmt = "{:<22} {:<16} {:>6} {:>7} {}"
5788
+ print(fmt.format("NAME", "STATUS", "FACTS", "ALERTS", "REL PATH"))
5789
+ print("-" * 76)
5790
+ for c in children:
5791
+ print(fmt.format(
5792
+ (c.get("name") or "?")[:21],
5793
+ c.get("status", "?"),
5794
+ c.get("facts_count", 0),
5795
+ c.get("notification_count", 0),
5796
+ c.get("rel_path", ""),
5797
+ ))
5798
+ issues = sum(1 for c in children if c["status"] != "ok")
5799
+ line = f"\n{len(children)} sub-project(s)"
5800
+ if issues:
5801
+ line += f" -- {issues} with issues (run `c3 sub check --fix`)"
5802
+ if report.get("orphans"):
5803
+ line += f" -- {len(report['orphans'])} registry orphan(s)"
5804
+ print(line)
5805
+
5806
+ elif sub == "remove":
5807
+ if not target:
5808
+ print("Usage: c3 sub remove <name|path> [--clear] [--yes]")
5809
+ return
5810
+ mode = "clear" if getattr(args, "clear", False) else "unlink"
5811
+ if mode == "clear" and not getattr(args, "yes", False):
5812
+ print("This will DELETE the sub-project's .c3 directory and instruction docs.")
5813
+ confirm = input("Type 'clear' to confirm: ").strip().lower()
5814
+ if confirm != "clear":
5815
+ print("Aborted.")
5816
+ return
5817
+ result = sm.remove(target, mode=mode,
5818
+ reindex_parent=not getattr(args, "no_reindex_parent", False))
5819
+ if as_json:
5820
+ print(json.dumps(result, indent=2))
5821
+ return
5822
+ if not result.get("removed"):
5823
+ print(f"Failed: {result.get('error')}")
5824
+ return
5825
+ print(f"\n[OK] {'Cleared' if mode == 'clear' else 'Unlinked'}: "
5826
+ f"{result.get('name')} ({result.get('path')})")
5827
+ for w in result.get("warnings", []):
5828
+ print(f" warning: {w}")
5829
+
5830
+ elif sub == "run":
5831
+ if target not in VALID_CASCADE_OPS:
5832
+ print(f"Usage: c3 sub run {{{'|'.join(VALID_CASCADE_OPS)}}} [--include-parent] [--json]")
5833
+ return
5834
+ result = sm.cascade(target,
5835
+ include_parent=getattr(args, "include_parent", False),
5836
+ mcp=getattr(args, "mcp", False))
5837
+ if as_json:
5838
+ print(json.dumps(result, indent=2))
5839
+ return
5840
+ for row in result["results"]:
5841
+ mark = "OK " if row["ok"] else "FAIL"
5842
+ extra = f" -- {row.get('error')}" if row.get("error") else ""
5843
+ print(f" [{mark}] {row['name']:<22} {row['elapsed_ms']:>6}ms{extra}")
5844
+ s = result["summary"]
5845
+ print(f"\n{target}: {s['ok']}/{s['total']} ok, {s['failed']} failed")
5846
+
5847
+ elif sub == "check":
5848
+ result = sm.reconcile(fix=getattr(args, "fix", False),
5849
+ prune=getattr(args, "prune", False))
5850
+ if as_json:
5851
+ print(json.dumps(result, indent=2))
5852
+ return
5853
+ if not result["children"] and not result["orphans"] and not result["pruned"]:
5854
+ print("No sub-projects designated.")
5855
+ return
5856
+ for c in result["children"]:
5857
+ print(f" [{c['status']:<16}] {c.get('name') or '?':<22} {c.get('rel_path', '')}")
5858
+ for o in result["orphans"]:
5859
+ print(f" [orphan_registry ] {o}")
5860
+ for f in result.get("fixed", []):
5861
+ print(f" fixed: {f.get('action')} -> {f.get('path') or f.get('rel_path')}")
5862
+ for p in result.get("pruned", []):
5863
+ print(f" pruned: {p.get('rel_path')}")
5864
+ if result["ok"]:
5865
+ print("\nAll links consistent.")
5866
+ else:
5867
+ hint = "" if getattr(args, "fix", False) else " Run `c3 sub check --fix` to repair."
5868
+ print(f"\nIssues found.{hint}")
5869
+
5870
+
5736
5871
  def cmd_session_benchmark(args):
5737
5872
  """Run real-world session workflow benchmark."""
5738
5873
  if getattr(args, "command", "") == "session-benchmark":
@@ -6577,6 +6712,7 @@ def main():
6577
6712
  "terse": cmd_terse,
6578
6713
  "ui": cmd_ui,
6579
6714
  "projects": cmd_projects,
6715
+ "sub": cmd_sub,
6580
6716
  "hub": cmd_hub,
6581
6717
  "bitbucket": cmd_bitbucket,
6582
6718
  "oracle": cmd_oracle,
cli/commands/parser.py CHANGED
@@ -138,6 +138,33 @@ def build_parser(version: str, parse_cli_ide_arg):
138
138
  )
139
139
  p_projects.add_argument("--name", default=None, help="Display name (for add)")
140
140
 
141
+ p_sub = subparsers.add_parser("sub", help="Manage sub-projects (linked child .c3 branches)")
142
+ p_sub.add_argument(
143
+ "sub_cmd",
144
+ nargs="?",
145
+ choices=["add", "list", "remove", "run", "check"],
146
+ default="list",
147
+ help="Sub-command (default: list)",
148
+ )
149
+ p_sub.add_argument(
150
+ "target",
151
+ nargs="?",
152
+ default=None,
153
+ help="Folder (add), sub-project name/path (remove), or operation update|reindex|health (run)",
154
+ )
155
+ p_sub.add_argument("--parent", default=".", help="Parent project path (default: current directory)")
156
+ p_sub.add_argument("--name", default=None, help="Display name for the sub-project (add)")
157
+ p_sub.add_argument("--ide", default=None, type=parse_cli_ide_arg, help="IDE for the sub-project init (add)")
158
+ p_sub.add_argument("--no-reindex-parent", action="store_true", help="Skip the parent reindex after add/remove")
159
+ p_sub.add_argument("--no-init", action="store_true", help="Link only; skip running init in the folder (add)")
160
+ p_sub.add_argument("--clear", action="store_true", help="Also wipe the sub-project's .c3 and unregister it (remove; default keeps .c3)")
161
+ p_sub.add_argument("--yes", action="store_true", help="Skip confirmation prompts")
162
+ p_sub.add_argument("--include-parent", action="store_true", help="Also run the operation on the parent (run)")
163
+ p_sub.add_argument("--mcp", action="store_true", help="Also reinstall MCP config on update (run update)")
164
+ p_sub.add_argument("--fix", action="store_true", help="Repair links from the parent config (check)")
165
+ p_sub.add_argument("--prune", action="store_true", help="With --fix: drop entries whose folder is gone (check)")
166
+ p_sub.add_argument("--json", action="store_true", help="Emit JSON output")
167
+
141
168
  p_perms = subparsers.add_parser("permissions",
142
169
  help="Manage Claude Code permissions — show | preview <tier> | diff | clean | <tier>")
143
170
  p_perms.add_argument("tier", nargs="?", default="show",