WP-0001: feedback channels, CI, pre-commit, telemetry docs
Some checks failed
ci / test (3.12) (push) Has been cancelled
ci / test (3.10) (push) Has been cancelled

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:
2026-06-16 01:58:07 +02:00
parent 79883aa25b
commit 80c60ebd7a
30 changed files with 556 additions and 110 deletions

View File

@@ -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.01.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)

View File

@@ -7,4 +7,4 @@ __all__ = [
"HelixCorrelationAdapter",
"enrich_helix_correlation",
"publish_optimizer_evidence",
]
]

View File

@@ -230,4 +230,4 @@ def _http_bytes(
def _quote(value: str) -> str:
return parse.quote(value, safe="")
return parse.quote(value, safe="")

View File

@@ -9,7 +9,6 @@ from dataclasses import dataclass
from pathlib import Path
from typing import Any, Dict, Optional
ENV_SESSION_UID = "HELIX_SESSION_UID"
ENV_REPO = "HELIX_REPO"
ENV_FLAVOR = "HELIX_FLAVOR"
@@ -168,4 +167,4 @@ class HelixCorrelationAdapter:
digest.setdefault("flavor", session.get("flavor"))
return digest
finally:
conn.close()
conn.close()

View File

@@ -8,7 +8,6 @@ from datetime import datetime, timedelta, timezone
from pathlib import Path
from typing import Any, Dict, List, Optional
DEFAULT_RETENTION_DAYS = 180
@@ -170,7 +169,9 @@ class MetricsStore:
recent_success = [1.0 if s else 0.0 for s in successes[-window:]]
prior_success = [1.0 if s else 0.0 for s in successes[:-window][-window:]]
recent_quality = quality_scores[-window:]
prior_quality = quality_scores[:-window][-window:] if len(quality_scores) > window else []
prior_quality = (
quality_scores[:-window][-window:] if len(quality_scores) > window else []
)
return {
"agent": self.agent_name,
@@ -274,4 +275,4 @@ class OptimizerStore:
}
with self.recommendations_path.open("a", encoding="utf-8") as handle:
handle.write(json.dumps(entry, sort_keys=True))
handle.write("\n")
handle.write("\n")

View File

@@ -135,7 +135,10 @@ class AgentDefinition:
return AgentCategory.META
# Infrastructure agents
if any(keyword in name_lower for keyword in ["setup", "repository", "tooling", "sys-medic", "medic"]):
if any(
keyword in name_lower
for keyword in ["setup", "repository", "tooling", "sys-medic", "medic"]
):
return AgentCategory.INFRASTRUCTURE
# Development process agents