WP-0001: feedback channels, CI, pre-commit, telemetry docs
Add kaizen-agentic feedback CLI, Gitea issue templates, CI workflow, pre-commit hooks, FEEDBACK/TELEMETRY docs, and cross-platform path tests. Improve CLI registry error messages; remove agents_backup scaffolding. Apply black formatting across src/tests for CI consistency. State Hub message sent to agentic-resources for Helix correlation doc link.
This commit is contained in:
@@ -2,7 +2,6 @@
|
||||
|
||||
import json
|
||||
import sys
|
||||
import subprocess
|
||||
import contextlib
|
||||
import io
|
||||
import click
|
||||
@@ -69,17 +68,22 @@ def safe_cli_wrapper():
|
||||
affected_commands = len(sys.argv) >= 2 and sys.argv[1] in ["install", "update"]
|
||||
|
||||
try:
|
||||
with contextlib.redirect_stderr(stderr_capture), contextlib.redirect_stdout(stdout_capture):
|
||||
with contextlib.redirect_stderr(stderr_capture), contextlib.redirect_stdout(
|
||||
stdout_capture
|
||||
):
|
||||
cli(standalone_mode=False)
|
||||
except click.UsageError as e:
|
||||
if affected_commands and "Got unexpected extra argument" in str(e):
|
||||
# This is the spurious error for install/update commands
|
||||
# Check if we got some stdout output indicating success
|
||||
captured_stdout = stdout_capture.getvalue()
|
||||
success_indicators = ["Installing agents to:", "Updating all installed agents:"]
|
||||
success_indicators = [
|
||||
"Installing agents to:",
|
||||
"Updating all installed agents:",
|
||||
]
|
||||
if any(indicator in captured_stdout for indicator in success_indicators):
|
||||
# The command was actually executing, show the real output
|
||||
print(captured_stdout, end='')
|
||||
print(captured_stdout, end="")
|
||||
sys.exit(0)
|
||||
else:
|
||||
# This might be a real error
|
||||
@@ -96,29 +100,45 @@ def safe_cli_wrapper():
|
||||
|
||||
if e.code == 0:
|
||||
# Successful exit
|
||||
print(captured_stdout, end='')
|
||||
print(captured_stdout, end="")
|
||||
else:
|
||||
# Error exit - show both stdout and stderr unless it's the spurious error
|
||||
if affected_commands and "Got unexpected extra argument" in captured_stderr:
|
||||
# Show only stdout for install/update commands with spurious errors
|
||||
print(captured_stdout, end='')
|
||||
success_indicators = ["Installing agents to:", "Updating all installed agents:"]
|
||||
if any(indicator in captured_stdout for indicator in success_indicators):
|
||||
print(captured_stdout, end="")
|
||||
success_indicators = [
|
||||
"Installing agents to:",
|
||||
"Updating all installed agents:",
|
||||
]
|
||||
if any(
|
||||
indicator in captured_stdout for indicator in success_indicators
|
||||
):
|
||||
sys.exit(0) # Override error exit if we see success indicators
|
||||
else:
|
||||
# Show everything for other commands
|
||||
print(captured_stdout, end='')
|
||||
print(captured_stderr, end='', file=sys.stderr)
|
||||
print(captured_stdout, end="")
|
||||
print(captured_stderr, end="", file=sys.stderr)
|
||||
sys.exit(e.code)
|
||||
except Exception as e:
|
||||
print(f"Error: {e}")
|
||||
sys.exit(1)
|
||||
|
||||
# If we get here, show captured output
|
||||
print(stdout_capture.getvalue(), end='')
|
||||
print(stdout_capture.getvalue(), end="")
|
||||
stderr_content = stderr_capture.getvalue()
|
||||
if stderr_content and not (affected_commands and "Got unexpected extra argument" in stderr_content):
|
||||
print(stderr_content, end='', file=sys.stderr)
|
||||
if stderr_content and not (
|
||||
affected_commands and "Got unexpected extra argument" in stderr_content
|
||||
):
|
||||
print(stderr_content, end="", file=sys.stderr)
|
||||
|
||||
|
||||
_FEEDBACK_CHANNELS = {
|
||||
"issues": "https://gitea.coulomb.social/coulomb/kaizen-agentic/issues",
|
||||
"issue_templates": "https://gitea.coulomb.social/coulomb/kaizen-agentic/issues/new/choose",
|
||||
"feedback_guide": "https://gitea.coulomb.social/coulomb/kaizen-agentic/src/branch/main/docs/FEEDBACK.md",
|
||||
"contributing": "https://gitea.coulomb.social/coulomb/kaizen-agentic/src/branch/main/CONTRIBUTING.md",
|
||||
}
|
||||
|
||||
|
||||
@click.group()
|
||||
@click.version_option()
|
||||
@@ -127,6 +147,32 @@ def cli():
|
||||
pass
|
||||
|
||||
|
||||
@cli.command("feedback")
|
||||
@click.option("--json", "as_json", is_flag=True, help="Emit machine-readable JSON")
|
||||
def feedback(as_json: bool):
|
||||
"""Show how to submit bugs, ideas, and adoption feedback."""
|
||||
payload = {
|
||||
"channels": _FEEDBACK_CHANNELS,
|
||||
"templates": ["bug_report", "feature_request", "feedback"],
|
||||
"cli_hint": "Use Gitea issue templates or State Hub messages for cross-repo coordination",
|
||||
}
|
||||
if as_json:
|
||||
click.echo(json.dumps(payload, indent=2, sort_keys=True))
|
||||
return
|
||||
|
||||
click.echo("Kaizen Agentic — feedback channels")
|
||||
click.echo("=" * 40)
|
||||
click.echo(f"Issues: {_FEEDBACK_CHANNELS['issues']}")
|
||||
click.echo(f"New issue: {_FEEDBACK_CHANNELS['issue_templates']}")
|
||||
click.echo(f"Feedback guide: {_FEEDBACK_CHANNELS['feedback_guide']}")
|
||||
click.echo(f"Contributing: {_FEEDBACK_CHANNELS['contributing']}")
|
||||
click.echo()
|
||||
click.echo("Templates: bug report · feature request · general feedback")
|
||||
click.echo(
|
||||
"Tip: include Python version and `kaizen-agentic --version` in bug reports."
|
||||
)
|
||||
|
||||
|
||||
@cli.command("list")
|
||||
@click.option(
|
||||
"--category",
|
||||
@@ -842,7 +888,9 @@ session_count: 0
|
||||
registry = _get_registry()
|
||||
protocols_dir = registry.agents_dir / "protocols" / agent_name
|
||||
if protocols_dir.exists():
|
||||
slugs = [f.stem for f in sorted(protocols_dir.glob("*.md")) if f.name != "README.md"]
|
||||
slugs = [
|
||||
f.stem for f in sorted(protocols_dir.glob("*.md")) if f.name != "README.md"
|
||||
]
|
||||
if slugs:
|
||||
click.echo(f" Protocols available for '{agent_name}':")
|
||||
for slug in slugs:
|
||||
@@ -852,7 +900,9 @@ session_count: 0
|
||||
@memory.command("brief")
|
||||
@click.argument("agent_name")
|
||||
@click.option("--target", "-t", default=".", help="Project root (default: current)")
|
||||
@click.option("--raw", is_flag=True, help="Dump raw memory files without synthesis header")
|
||||
@click.option(
|
||||
"--raw", is_flag=True, help="Dump raw memory files without synthesis header"
|
||||
)
|
||||
def memory_brief(agent_name: str, target: str, raw: bool):
|
||||
"""Print a coach-synthesised orientation for an agent.
|
||||
|
||||
@@ -889,6 +939,7 @@ def memory_brief(agent_name: str, target: str, raw: bool):
|
||||
return
|
||||
|
||||
from datetime import date as _date
|
||||
|
||||
today = _date.today().isoformat()
|
||||
sources = ([agent_name] if own_memory else []) + list(other_memories.keys())
|
||||
|
||||
@@ -918,7 +969,9 @@ def memory_brief(agent_name: str, target: str, raw: bool):
|
||||
click.echo("### Your Memory")
|
||||
click.echo(own_memory)
|
||||
else:
|
||||
click.echo(f"### Your Memory\n(none — run: kaizen-agentic memory init {agent_name})\n")
|
||||
click.echo(
|
||||
f"### Your Memory\n(none — run: kaizen-agentic memory init {agent_name})\n"
|
||||
)
|
||||
|
||||
# Cross-agent context
|
||||
if other_memories:
|
||||
@@ -928,17 +981,23 @@ def memory_brief(agent_name: str, target: str, raw: bool):
|
||||
click.echo(f"--- {name} ---")
|
||||
click.echo(content)
|
||||
else:
|
||||
click.echo("### Context From Other Agents\nNo other agent memories found in this project.\n")
|
||||
click.echo(
|
||||
"### Context From Other Agents\nNo other agent memories found in this project.\n"
|
||||
)
|
||||
|
||||
click.echo("---")
|
||||
click.echo("Tip: Load agents/agent-coach.md in your Claude session and pass this output")
|
||||
click.echo(
|
||||
"Tip: Load agents/agent-coach.md in your Claude session and pass this output"
|
||||
)
|
||||
click.echo(" for a full cross-agent synthesis and orientation brief.")
|
||||
|
||||
|
||||
@memory.command("clear")
|
||||
@click.argument("agent_name")
|
||||
@click.option("--target", "-t", default=".", help="Project root (default: current)")
|
||||
@click.confirmation_option(prompt="This will permanently delete the agent memory. Continue?")
|
||||
@click.confirmation_option(
|
||||
prompt="This will permanently delete the agent memory. Continue?"
|
||||
)
|
||||
def memory_clear(agent_name: str, target: str):
|
||||
"""Wipe agent memory for the current project."""
|
||||
memory_path = _memory_path(target, agent_name)
|
||||
@@ -964,13 +1023,19 @@ def metrics():
|
||||
@metrics.command("record")
|
||||
@click.argument("agent_name")
|
||||
@click.option("--target", "-t", default=".", help="Project root (default: current)")
|
||||
@click.option("--success", "outcome_success", is_flag=True, help="Record successful execution")
|
||||
@click.option("--failure", "outcome_failure", is_flag=True, help="Record failed execution")
|
||||
@click.option(
|
||||
"--success", "outcome_success", is_flag=True, help="Record successful execution"
|
||||
)
|
||||
@click.option(
|
||||
"--failure", "outcome_failure", is_flag=True, help="Record failed execution"
|
||||
)
|
||||
@click.option("--time", "execution_time", type=float, help="Execution time in seconds")
|
||||
@click.option("--quality", type=float, help="Quality score 0.0–1.0")
|
||||
@click.option("--session-id", help="Optional session identifier")
|
||||
@click.option("--idempotency-key", help="Skip append if this key was already recorded")
|
||||
@click.option("--json", "json_input", is_flag=True, help="Read full record JSON from stdin")
|
||||
@click.option(
|
||||
"--json", "json_input", is_flag=True, help="Read full record JSON from stdin"
|
||||
)
|
||||
def metrics_record(
|
||||
agent_name: str,
|
||||
target: str,
|
||||
@@ -995,7 +1060,9 @@ def metrics_record(
|
||||
click.echo("Error: use only one of --success or --failure", err=True)
|
||||
sys.exit(1)
|
||||
if not outcome_success and not outcome_failure:
|
||||
click.echo("Error: specify --success or --failure (or use --json)", err=True)
|
||||
click.echo(
|
||||
"Error: specify --success or --failure (or use --json)", err=True
|
||||
)
|
||||
sys.exit(1)
|
||||
payload = {"success": outcome_success}
|
||||
if execution_time is not None:
|
||||
@@ -1010,13 +1077,17 @@ def metrics_record(
|
||||
if store.append(payload, idempotency_key=idempotency_key):
|
||||
click.echo(f"Recorded metrics for '{agent_name}'")
|
||||
else:
|
||||
click.echo(f"Skipped duplicate record for '{agent_name}' (idempotency key exists)")
|
||||
click.echo(
|
||||
f"Skipped duplicate record for '{agent_name}' (idempotency key exists)"
|
||||
)
|
||||
|
||||
|
||||
@metrics.command("show")
|
||||
@click.argument("agent_name")
|
||||
@click.option("--target", "-t", default=".", help="Project root (default: current)")
|
||||
@click.option("--limit", "-n", default=5, show_default=True, help="Recent executions to show")
|
||||
@click.option(
|
||||
"--limit", "-n", default=5, show_default=True, help="Recent executions to show"
|
||||
)
|
||||
def metrics_show(agent_name: str, target: str, limit: int):
|
||||
"""Print metrics summary and recent executions for an agent."""
|
||||
store = MetricsStore(_project_root(target), agent_name)
|
||||
@@ -1073,7 +1144,9 @@ def metrics_optimize(agent_name: Optional[str], target: str, min_samples: int):
|
||||
|
||||
if not agents:
|
||||
click.echo("No agent metrics found to optimize.")
|
||||
click.echo(" Record executions with: kaizen-agentic metrics record <agent> --success")
|
||||
click.echo(
|
||||
" Record executions with: kaizen-agentic metrics record <agent> --success"
|
||||
)
|
||||
return
|
||||
|
||||
optimizer_store = OptimizerStore(project_root)
|
||||
@@ -1287,6 +1360,7 @@ def _memory_path(target: str, agent_name: str) -> Path:
|
||||
|
||||
def _today() -> str:
|
||||
from datetime import date
|
||||
|
||||
return date.today().isoformat()
|
||||
|
||||
|
||||
@@ -1312,14 +1386,20 @@ def _get_registry() -> AgentRegistry:
|
||||
# Try relative to package
|
||||
agents_dir = Path(kaizen_agentic.__file__).parent / "data" / "agents"
|
||||
except ImportError:
|
||||
click.echo("Error: Could not find agents directory")
|
||||
click.echo(
|
||||
"Make sure you're in a kaizen-agentic project or have the package installed"
|
||||
)
|
||||
click.echo("Error: kaizen-agentic package is not installed.", err=True)
|
||||
click.echo(" Fix: pip install -e . (from repo root)", err=True)
|
||||
click.echo(" Or: run from a project with an agents/ directory", err=True)
|
||||
sys.exit(1)
|
||||
|
||||
if not agents_dir.exists():
|
||||
click.echo(f"Error: Agents directory not found: {agents_dir}")
|
||||
click.echo(f"Error: agents directory not found: {agents_dir}", err=True)
|
||||
click.echo(
|
||||
" Fix: cd into a kaizen-agentic checkout or a project with agents/",
|
||||
err=True,
|
||||
)
|
||||
click.echo(
|
||||
" Or: kaizen-agentic install <template> to scaffold agents", err=True
|
||||
)
|
||||
sys.exit(1)
|
||||
|
||||
return AgentRegistry(agents_dir)
|
||||
|
||||
Reference in New Issue
Block a user