conduct-cli 0.4.67__tar.gz → 0.4.69__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 (24) hide show
  1. {conduct_cli-0.4.67 → conduct_cli-0.4.69}/PKG-INFO +1 -1
  2. {conduct_cli-0.4.67 → conduct_cli-0.4.69}/pyproject.toml +1 -1
  3. {conduct_cli-0.4.67 → conduct_cli-0.4.69}/src/conduct_cli/guardmcp.py +2 -2
  4. {conduct_cli-0.4.67 → conduct_cli-0.4.69}/src/conduct_cli/hook_precompact_template.py +7 -3
  5. {conduct_cli-0.4.67 → conduct_cli-0.4.69}/src/conduct_cli/main.py +130 -0
  6. {conduct_cli-0.4.67 → conduct_cli-0.4.69}/src/conduct_cli.egg-info/PKG-INFO +1 -1
  7. {conduct_cli-0.4.67 → conduct_cli-0.4.69}/README.md +0 -0
  8. {conduct_cli-0.4.67 → conduct_cli-0.4.69}/setup.cfg +0 -0
  9. {conduct_cli-0.4.67 → conduct_cli-0.4.69}/setup.py +0 -0
  10. {conduct_cli-0.4.67 → conduct_cli-0.4.69}/src/conduct_cli/__init__.py +0 -0
  11. {conduct_cli-0.4.67 → conduct_cli-0.4.69}/src/conduct_cli/api.py +0 -0
  12. {conduct_cli-0.4.67 → conduct_cli-0.4.69}/src/conduct_cli/guard.py +0 -0
  13. {conduct_cli-0.4.67 → conduct_cli-0.4.69}/src/conduct_cli/hook_session_start_template.py +0 -0
  14. {conduct_cli-0.4.67 → conduct_cli-0.4.69}/src/conduct_cli/hook_template.py +0 -0
  15. {conduct_cli-0.4.67 → conduct_cli-0.4.69}/src/conduct_cli/mcp_server.py +0 -0
  16. {conduct_cli-0.4.67 → conduct_cli-0.4.69}/src/conduct_cli.egg-info/SOURCES.txt +0 -0
  17. {conduct_cli-0.4.67 → conduct_cli-0.4.69}/src/conduct_cli.egg-info/dependency_links.txt +0 -0
  18. {conduct_cli-0.4.67 → conduct_cli-0.4.69}/src/conduct_cli.egg-info/entry_points.txt +0 -0
  19. {conduct_cli-0.4.67 → conduct_cli-0.4.69}/src/conduct_cli.egg-info/requires.txt +0 -0
  20. {conduct_cli-0.4.67 → conduct_cli-0.4.69}/src/conduct_cli.egg-info/top_level.txt +0 -0
  21. {conduct_cli-0.4.67 → conduct_cli-0.4.69}/tests/test_guard_policy.py +0 -0
  22. {conduct_cli-0.4.67 → conduct_cli-0.4.69}/tests/test_guard_savings.py +0 -0
  23. {conduct_cli-0.4.67 → conduct_cli-0.4.69}/tests/test_hook_syntax.py +0 -0
  24. {conduct_cli-0.4.67 → conduct_cli-0.4.69}/tests/test_switch.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: conduct-cli
3
- Version: 0.4.67
3
+ Version: 0.4.69
4
4
  Summary: CLI for Conduct AI — install agents, manage projects, run tests
5
5
  Author-email: Conduct AI <hello@conductai.ai>
6
6
  License: MIT
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "conduct-cli"
7
- version = "0.4.67"
7
+ version = "0.4.69"
8
8
  description = "CLI for Conduct AI — install agents, manage projects, run tests"
9
9
  readme = "README.md"
10
10
  license = { text = "MIT" }
@@ -2,8 +2,8 @@
2
2
  """
3
3
  conductguard-mcp — ConductGuard MCP server.
4
4
 
5
- Runs as a subprocess started by Claude Code / Cursor / Windsurf via the
6
- mcpServers config written by `conduct guard sync`. Communicates over
5
+ Runs as a subprocess started by Claude Code / Codex / Cursor / Windsurf via
6
+ the mcpServers config written by `conduct guard sync`. Communicates over
7
7
  stdin/stdout using JSON-RPC 2.0 (MCP stdio transport).
8
8
 
9
9
  Exposes three tools:
@@ -35,9 +35,13 @@ def _memory_headline():
35
35
  try:
36
36
  root = Path.cwd()
37
37
  mem_key = str(root).replace("/", "-").lstrip("-")
38
- mem_path = Path.home() / ".claude" / "projects" / mem_key / "memory" / "MEMORY.md"
39
- if mem_path.exists():
40
- return "\n".join(mem_path.read_text().splitlines()[:10])
38
+ candidates = [
39
+ Path.home() / ".claude" / "projects" / mem_key / "memory" / "MEMORY.md",
40
+ Path.home() / ".codex" / "projects" / mem_key / "memory" / "MEMORY.md",
41
+ ]
42
+ for mem_path in candidates:
43
+ if mem_path.exists():
44
+ return "\n".join(mem_path.read_text().splitlines()[:10])
41
45
  except Exception:
42
46
  pass
43
47
  return ""
@@ -2126,6 +2126,132 @@ def classify_finding(text: str) -> "dict | None":
2126
2126
  return None
2127
2127
 
2128
2128
 
2129
+ def cmd_session_report(args):
2130
+ """
2131
+ Run paxel-local against local Claude Code transcripts and send a
2132
+ developer profile report to the workspace admin via the Conduct API.
2133
+ """
2134
+ import json as _json
2135
+ import shutil
2136
+ import subprocess
2137
+ import tempfile
2138
+ import urllib.request
2139
+
2140
+ server, workspace_id, api_key, token = _require_auth(args)
2141
+ hdrs = api.headers(workspace_id, token, "application/json", api_key)
2142
+
2143
+ # ── 1. Fetch paxel-local ─────────────────────────────────────────────────
2144
+ PAXEL_URL = "https://raw.githubusercontent.com/Photobombastic/paxel-local/main/paxel.py"
2145
+ tmpdir = tempfile.mkdtemp(prefix="conduct-paxel-")
2146
+ paxel_script = Path(tmpdir) / "paxel.py"
2147
+
2148
+ print("Downloading paxel-local…")
2149
+ try:
2150
+ urllib.request.urlretrieve(PAXEL_URL, paxel_script)
2151
+ except Exception as exc:
2152
+ print(f"ERROR: could not download paxel-local: {exc}")
2153
+ shutil.rmtree(tmpdir, ignore_errors=True)
2154
+ sys.exit(1)
2155
+
2156
+ # ── 2. Run paxel ─────────────────────────────────────────────────────────
2157
+ print("Analysing sessions…")
2158
+ try:
2159
+ result = subprocess.run(
2160
+ [sys.executable, str(paxel_script), "--no-open"],
2161
+ cwd=tmpdir,
2162
+ capture_output=True,
2163
+ text=True,
2164
+ timeout=120,
2165
+ )
2166
+ except subprocess.TimeoutExpired:
2167
+ print("ERROR: paxel analysis timed out after 120 s.")
2168
+ shutil.rmtree(tmpdir, ignore_errors=True)
2169
+ sys.exit(1)
2170
+
2171
+ stats_path = Path(tmpdir) / "stats.json"
2172
+ report_path = Path(tmpdir) / "report.md"
2173
+
2174
+ if not stats_path.exists():
2175
+ print("ERROR: paxel did not produce stats.json — no transcripts found?")
2176
+ if result.stderr:
2177
+ print(result.stderr[:500])
2178
+ shutil.rmtree(tmpdir, ignore_errors=True)
2179
+ sys.exit(1)
2180
+
2181
+ with open(stats_path) as f:
2182
+ stats = _json.load(f)
2183
+
2184
+ report_md = report_path.read_text() if report_path.exists() else ""
2185
+
2186
+ # ── 3. Build summary payload ─────────────────────────────────────────────
2187
+ import getpass, socket
2188
+ developer = getattr(args, "developer", None) or getpass.getuser()
2189
+ hostname = socket.gethostname()
2190
+
2191
+ volume = stats.get("volume", {})
2192
+ behavior = stats.get("behavior", {})
2193
+ autonomy = stats.get("autonomy", {})
2194
+ tools = stats.get("tools", {})
2195
+ velocity = stats.get("velocity", {})
2196
+
2197
+ sessions = volume.get("total_sessions", "?")
2198
+ prompts = volume.get("total_prompts", "?")
2199
+ autonomy_score = autonomy.get("autonomy_score_0_100", "?")
2200
+ top_tools = [t[0] for t in (tools.get("top_tools") or [])[:5]]
2201
+ commits = velocity.get("git_commits_real", "?") if isinstance(velocity, dict) else "?"
2202
+
2203
+ # Derive a simple archetype label from the data
2204
+ planning_ratio = behavior.get("planning_ratio_explore_to_doing", 0)
2205
+ if autonomy_score != "?" and float(autonomy_score) >= 70:
2206
+ archetype = "Autonomous Builder"
2207
+ elif planning_ratio != 0 and float(planning_ratio) > 1.0:
2208
+ archetype = "Strategic Planner"
2209
+ else:
2210
+ archetype = "Execution-Focused Builder"
2211
+
2212
+ payload = {
2213
+ "developer": developer,
2214
+ "hostname": hostname,
2215
+ "archetype": archetype,
2216
+ "competency_scores": {
2217
+ "autonomy": autonomy_score,
2218
+ "planning_ratio": planning_ratio,
2219
+ "commits": commits,
2220
+ },
2221
+ "total_sessions": sessions,
2222
+ "tools_detected": top_tools,
2223
+ "report_md": report_md[:4000],
2224
+ "raw_stats": {
2225
+ "volume": volume,
2226
+ "autonomy": autonomy,
2227
+ "top_tools": top_tools,
2228
+ "velocity": velocity,
2229
+ "scope": stats.get("scope", ""),
2230
+ },
2231
+ }
2232
+
2233
+ # ── 4. Send to Conduct API ────────────────────────────────────────────────
2234
+ print("Sending report to admin…")
2235
+ resp = api.req(
2236
+ "POST",
2237
+ f"{server}/session-reports",
2238
+ hdrs,
2239
+ body=payload,
2240
+ )
2241
+
2242
+ shutil.rmtree(tmpdir, ignore_errors=True)
2243
+
2244
+ if resp and resp.get("id"):
2245
+ print(f"\nReport sent.")
2246
+ print(f" Archetype : {archetype}")
2247
+ print(f" Autonomy : {autonomy_score}/100 Planning ratio: {planning_ratio}")
2248
+ print(f" Sessions : {sessions} Prompts: {prompts} Commits: {commits}")
2249
+ if top_tools:
2250
+ print(f" Top tools : {', '.join(top_tools)}")
2251
+ else:
2252
+ print(f"WARNING: server response unexpected: {resp}")
2253
+
2254
+
2129
2255
  def cmd_emit_finding(args):
2130
2256
  """POST a security finding to /security-findings."""
2131
2257
  server, workspace_id, api_key, token = _require_auth(args)
@@ -2730,6 +2856,8 @@ def main():
2730
2856
  sub.add_parser("test-guard", help="Fire a synthetic event per guard policy rule and show decisions")
2731
2857
  sub.add_parser("test-security", help="Post a synthetic finding per security classifier pattern")
2732
2858
  sub.add_parser("test-security-verify", help="Post test findings and verify full triage pipeline end-to-end")
2859
+ sr_p = sub.add_parser("session-report", help="Analyse local AI coding sessions with paxel and send report to admin")
2860
+ sr_p.add_argument("--developer", default=None, help="Developer name (defaults to OS username)")
2733
2861
 
2734
2862
  args = parser.parse_args()
2735
2863
 
@@ -2806,6 +2934,8 @@ def main():
2806
2934
  cmd_test_security(args)
2807
2935
  elif args.command == "test-security-verify":
2808
2936
  cmd_test_security_verify(args)
2937
+ elif args.command == "session-report":
2938
+ cmd_session_report(args)
2809
2939
  else:
2810
2940
  parser.print_help()
2811
2941
 
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: conduct-cli
3
- Version: 0.4.67
3
+ Version: 0.4.69
4
4
  Summary: CLI for Conduct AI — install agents, manage projects, run tests
5
5
  Author-email: Conduct AI <hello@conductai.ai>
6
6
  License: MIT
File without changes
File without changes
File without changes