agentkit-cli 0.1.0__tar.gz → 0.2.0__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.
- agentkit_cli-0.2.0/.github/workflows/examples/agentkit-pipeline.yml +23 -0
- agentkit_cli-0.2.0/BUILD-REPORT.md +91 -0
- {agentkit_cli-0.1.0 → agentkit_cli-0.2.0}/CHANGELOG.md +9 -0
- {agentkit_cli-0.1.0 → agentkit_cli-0.2.0}/PKG-INFO +41 -1
- {agentkit_cli-0.1.0 → agentkit_cli-0.2.0}/README.md +40 -0
- agentkit_cli-0.2.0/action.yml +60 -0
- {agentkit_cli-0.1.0 → agentkit_cli-0.2.0}/agentkit_cli/__init__.py +1 -1
- agentkit_cli-0.2.0/agentkit_cli/commands/doctor_cmd.py +73 -0
- {agentkit_cli-0.1.0 → agentkit_cli-0.2.0}/agentkit_cli/commands/run_cmd.py +45 -18
- {agentkit_cli-0.1.0 → agentkit_cli-0.2.0}/agentkit_cli/main.py +9 -0
- agentkit_cli-0.2.0/memory/contracts/agentkit-cli-v0.2.0-doctor-action.md +137 -0
- {agentkit_cli-0.1.0 → agentkit_cli-0.2.0}/pyproject.toml +1 -1
- agentkit_cli-0.2.0/tests/test_action.py +74 -0
- agentkit_cli-0.2.0/tests/test_doctor.py +129 -0
- {agentkit_cli-0.1.0 → agentkit_cli-0.2.0}/tests/test_run.py +120 -0
- {agentkit_cli-0.1.0 → agentkit_cli-0.2.0}/LICENSE +0 -0
- {agentkit_cli-0.1.0 → agentkit_cli-0.2.0}/agentkit_cli/commands/__init__.py +0 -0
- {agentkit_cli-0.1.0 → agentkit_cli-0.2.0}/agentkit_cli/commands/init_cmd.py +0 -0
- {agentkit_cli-0.1.0 → agentkit_cli-0.2.0}/agentkit_cli/commands/status_cmd.py +0 -0
- {agentkit_cli-0.1.0 → agentkit_cli-0.2.0}/agentkit_cli/config.py +0 -0
- {agentkit_cli-0.1.0 → agentkit_cli-0.2.0}/agentkit_cli/tools.py +0 -0
- {agentkit_cli-0.1.0 → agentkit_cli-0.2.0}/tests/__init__.py +0 -0
- {agentkit_cli-0.1.0 → agentkit_cli-0.2.0}/tests/test_init.py +0 -0
- {agentkit_cli-0.1.0 → agentkit_cli-0.2.0}/tests/test_main.py +0 -0
- {agentkit_cli-0.1.0 → agentkit_cli-0.2.0}/tests/test_status.py +0 -0
- {agentkit_cli-0.1.0 → agentkit_cli-0.2.0}/tests/test_tools.py +0 -0
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
name: Agent Quality Pipeline
|
|
2
|
+
|
|
3
|
+
on:
|
|
4
|
+
push:
|
|
5
|
+
branches: [main, develop]
|
|
6
|
+
pull_request:
|
|
7
|
+
branches: [main]
|
|
8
|
+
|
|
9
|
+
jobs:
|
|
10
|
+
agentkit:
|
|
11
|
+
runs-on: ubuntu-latest
|
|
12
|
+
name: Run agentkit pipeline
|
|
13
|
+
steps:
|
|
14
|
+
- name: Checkout code
|
|
15
|
+
uses: actions/checkout@v4
|
|
16
|
+
|
|
17
|
+
- name: Run agentkit pipeline
|
|
18
|
+
uses: mikiships/agentkit-cli@v0.2.0
|
|
19
|
+
with:
|
|
20
|
+
python-version: '3.12'
|
|
21
|
+
skip: ''
|
|
22
|
+
benchmark: 'false'
|
|
23
|
+
fail-on-lint: 'true'
|
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
# BUILD-REPORT.md — agentkit-cli v0.1.0
|
|
2
|
+
|
|
3
|
+
**Built:** 2026-03-12
|
|
4
|
+
**Builder:** Mordecai (subagent)
|
|
5
|
+
|
|
6
|
+
---
|
|
7
|
+
|
|
8
|
+
## What Was Built
|
|
9
|
+
|
|
10
|
+
All 5 deliverables completed per contract.
|
|
11
|
+
|
|
12
|
+
### D1: Project Scaffold
|
|
13
|
+
- `~/repos/agentkit-cli/` created with `pyproject.toml` (hatchling build backend)
|
|
14
|
+
- Package: `agentkit-cli`, importable as `agentkit_cli`
|
|
15
|
+
- CLI entrypoint: `agentkit`
|
|
16
|
+
- Dependencies: `typer>=0.9.0`, `rich>=13.0.0` only
|
|
17
|
+
- MIT license, README.md, CHANGELOG.md
|
|
18
|
+
|
|
19
|
+
### D2: `agentkit init`
|
|
20
|
+
- Detects git root or falls back to cwd
|
|
21
|
+
- Checks all 4 quartet tools via `shutil.which()`
|
|
22
|
+
- Creates `.agentkit.yaml` with default config (skips if already exists)
|
|
23
|
+
- Shows install hints for missing tools
|
|
24
|
+
- Prints "Next Steps" panel
|
|
25
|
+
|
|
26
|
+
### D3: `agentkit run`
|
|
27
|
+
- Sequential 5-step pipeline: generate → lint-context → lint-diff → benchmark → reflect
|
|
28
|
+
- `--skip <step>` flag to skip individual steps
|
|
29
|
+
- `--benchmark` flag to opt-in to coderace step (skipped by default)
|
|
30
|
+
- `--json` flag for machine-readable summary output
|
|
31
|
+
- `--notes` flag passed to agentreflect
|
|
32
|
+
- Saves `.agentkit-last-run.json` after each run
|
|
33
|
+
- Rich table output; non-zero exit on failures
|
|
34
|
+
|
|
35
|
+
### D4: `agentkit status`
|
|
36
|
+
- Shows all quartet tools with install status, version, and path
|
|
37
|
+
- Shows `.agentkit.yaml`, `CLAUDE.md`, `.agentkit-last-run.json` presence
|
|
38
|
+
- Shows last run summary if available
|
|
39
|
+
- `--json` flag for machine-readable output
|
|
40
|
+
|
|
41
|
+
### D5: Tests, Docs, Publish
|
|
42
|
+
- 47 tests across 5 files — all passing
|
|
43
|
+
- README.md with framing, pipeline diagram, usage examples, quartet links
|
|
44
|
+
- CHANGELOG.md with v0.1.0 entry
|
|
45
|
+
- Published to PyPI
|
|
46
|
+
|
|
47
|
+
---
|
|
48
|
+
|
|
49
|
+
## Test Count
|
|
50
|
+
|
|
51
|
+
- **Total tests:** 47
|
|
52
|
+
- **Passed:** 47
|
|
53
|
+
- **Failed:** 0
|
|
54
|
+
|
|
55
|
+
Test files:
|
|
56
|
+
- `tests/test_init.py` — 9 tests
|
|
57
|
+
- `tests/test_run.py` — 13 tests
|
|
58
|
+
- `tests/test_status.py` — 13 tests
|
|
59
|
+
- `tests/test_tools.py` — 8 tests
|
|
60
|
+
- `tests/test_main.py` — 6 tests
|
|
61
|
+
|
|
62
|
+
---
|
|
63
|
+
|
|
64
|
+
## PyPI
|
|
65
|
+
|
|
66
|
+
**URL:** https://pypi.org/project/agentkit-cli/0.1.0/
|
|
67
|
+
|
|
68
|
+
```bash
|
|
69
|
+
pip install agentkit-cli==0.1.0
|
|
70
|
+
```
|
|
71
|
+
|
|
72
|
+
---
|
|
73
|
+
|
|
74
|
+
## Deviations from Contract
|
|
75
|
+
|
|
76
|
+
None. All contract requirements met:
|
|
77
|
+
- subprocess only (no quartet Python imports)
|
|
78
|
+
- 25+ tests (47 delivered)
|
|
79
|
+
- typer + rich only as hard deps
|
|
80
|
+
- All 3 commands (init, run, status) implemented
|
|
81
|
+
- `.agentkit.yaml` created by init
|
|
82
|
+
- `--json` flags on run and status
|
|
83
|
+
- Missing tools handled gracefully with install hints
|
|
84
|
+
|
|
85
|
+
---
|
|
86
|
+
|
|
87
|
+
## Issues Encountered
|
|
88
|
+
|
|
89
|
+
1. **Rich console wrapping long paths in JSON output** — rich wraps lines at terminal width, which inserted newlines into JSON strings causing `json.JSONDecodeError`. Fixed by using `print(json.dumps(...))` (stdlib) for JSON output instead of `console.print()`.
|
|
90
|
+
|
|
91
|
+
2. **Externally-managed Python environment** — system pip3 refused `pip install`. Used `python3 -m venv .venv` pattern. Build and twine upload ran via `.venv/bin/`.
|
|
@@ -1,5 +1,14 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## v0.2.0 (2026-03-12)
|
|
4
|
+
|
|
5
|
+
### Added
|
|
6
|
+
- `agentkit doctor` — diagnose quartet tool installation with Rich table output, `--json` flag, exits 1 on missing tools
|
|
7
|
+
- GitHub Action (`action.yml`) — composite action to run agentkit pipeline in CI with configurable inputs
|
|
8
|
+
- Example workflow (`.github/workflows/examples/agentkit-pipeline.yml`)
|
|
9
|
+
- Improved `agentkit run` summary table with ✓/✗/⊘ status symbols and `X/Y steps passed` line
|
|
10
|
+
- `summary` key in `agentkit run --json` output with structured step results
|
|
11
|
+
|
|
3
12
|
## v0.1.0 (2026-03-12)
|
|
4
13
|
|
|
5
14
|
Initial release.
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: agentkit-cli
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.2.0
|
|
4
4
|
Summary: Unified CLI for the Agent Quality Toolkit (agentmd, coderace, agentlint, agentreflect)
|
|
5
5
|
Project-URL: Homepage, https://github.com/mikiships/agentkit-cli
|
|
6
6
|
Project-URL: Repository, https://github.com/mikiships/agentkit-cli
|
|
@@ -134,6 +134,46 @@ agentkit status --json
|
|
|
134
134
|
|
|
135
135
|
---
|
|
136
136
|
|
|
137
|
+
### `agentkit doctor`
|
|
138
|
+
|
|
139
|
+
Diagnose whether all quartet tools are installed and functional.
|
|
140
|
+
|
|
141
|
+
```bash
|
|
142
|
+
agentkit doctor
|
|
143
|
+
agentkit doctor --json
|
|
144
|
+
```
|
|
145
|
+
|
|
146
|
+
Outputs a Rich table with ✓/✗ per tool, version, and install command. Exits 1 if any tool is missing.
|
|
147
|
+
|
|
148
|
+
---
|
|
149
|
+
|
|
150
|
+
## CI Integration
|
|
151
|
+
|
|
152
|
+
Use the agentkit GitHub Action to run the full pipeline in CI:
|
|
153
|
+
|
|
154
|
+
```yaml
|
|
155
|
+
- name: Run agentkit pipeline
|
|
156
|
+
uses: mikiships/agentkit-cli@v0.2.0
|
|
157
|
+
with:
|
|
158
|
+
python-version: '3.12'
|
|
159
|
+
skip: ''
|
|
160
|
+
benchmark: 'false'
|
|
161
|
+
fail-on-lint: 'true'
|
|
162
|
+
```
|
|
163
|
+
|
|
164
|
+
**Inputs:**
|
|
165
|
+
|
|
166
|
+
| Input | Default | Description |
|
|
167
|
+
|-------|---------|-------------|
|
|
168
|
+
| `skip` | `''` | Comma-separated steps to skip (`generate`, `lint`, `benchmark`, `reflect`) |
|
|
169
|
+
| `benchmark` | `false` | Enable coderace benchmark step |
|
|
170
|
+
| `python-version` | `3.12` | Python version to use |
|
|
171
|
+
| `fail-on-lint` | `true` | Exit 1 on agentlint failures |
|
|
172
|
+
|
|
173
|
+
See [`.github/workflows/examples/agentkit-pipeline.yml`](.github/workflows/examples/agentkit-pipeline.yml) for a full example.
|
|
174
|
+
|
|
175
|
+
---
|
|
176
|
+
|
|
137
177
|
## Links
|
|
138
178
|
|
|
139
179
|
- [agentmd](https://pypi.org/project/agentmd/)
|
|
@@ -109,6 +109,46 @@ agentkit status --json
|
|
|
109
109
|
|
|
110
110
|
---
|
|
111
111
|
|
|
112
|
+
### `agentkit doctor`
|
|
113
|
+
|
|
114
|
+
Diagnose whether all quartet tools are installed and functional.
|
|
115
|
+
|
|
116
|
+
```bash
|
|
117
|
+
agentkit doctor
|
|
118
|
+
agentkit doctor --json
|
|
119
|
+
```
|
|
120
|
+
|
|
121
|
+
Outputs a Rich table with ✓/✗ per tool, version, and install command. Exits 1 if any tool is missing.
|
|
122
|
+
|
|
123
|
+
---
|
|
124
|
+
|
|
125
|
+
## CI Integration
|
|
126
|
+
|
|
127
|
+
Use the agentkit GitHub Action to run the full pipeline in CI:
|
|
128
|
+
|
|
129
|
+
```yaml
|
|
130
|
+
- name: Run agentkit pipeline
|
|
131
|
+
uses: mikiships/agentkit-cli@v0.2.0
|
|
132
|
+
with:
|
|
133
|
+
python-version: '3.12'
|
|
134
|
+
skip: ''
|
|
135
|
+
benchmark: 'false'
|
|
136
|
+
fail-on-lint: 'true'
|
|
137
|
+
```
|
|
138
|
+
|
|
139
|
+
**Inputs:**
|
|
140
|
+
|
|
141
|
+
| Input | Default | Description |
|
|
142
|
+
|-------|---------|-------------|
|
|
143
|
+
| `skip` | `''` | Comma-separated steps to skip (`generate`, `lint`, `benchmark`, `reflect`) |
|
|
144
|
+
| `benchmark` | `false` | Enable coderace benchmark step |
|
|
145
|
+
| `python-version` | `3.12` | Python version to use |
|
|
146
|
+
| `fail-on-lint` | `true` | Exit 1 on agentlint failures |
|
|
147
|
+
|
|
148
|
+
See [`.github/workflows/examples/agentkit-pipeline.yml`](.github/workflows/examples/agentkit-pipeline.yml) for a full example.
|
|
149
|
+
|
|
150
|
+
---
|
|
151
|
+
|
|
112
152
|
## Links
|
|
113
153
|
|
|
114
154
|
- [agentmd](https://pypi.org/project/agentmd/)
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
name: agentkit pipeline
|
|
2
|
+
description: Run the full Agent Quality pipeline (agentmd, agentlint, coderace, agentreflect) on your repo.
|
|
3
|
+
author: mikiships
|
|
4
|
+
|
|
5
|
+
inputs:
|
|
6
|
+
skip:
|
|
7
|
+
description: Comma-separated pipeline steps to skip (generate, lint, benchmark, reflect)
|
|
8
|
+
required: false
|
|
9
|
+
default: ''
|
|
10
|
+
benchmark:
|
|
11
|
+
description: Enable benchmark step (coderace). Set to 'true' to enable.
|
|
12
|
+
required: false
|
|
13
|
+
default: 'false'
|
|
14
|
+
python-version:
|
|
15
|
+
description: Python version to use
|
|
16
|
+
required: false
|
|
17
|
+
default: '3.12'
|
|
18
|
+
fail-on-lint:
|
|
19
|
+
description: Exit 1 if agentlint reports failures
|
|
20
|
+
required: false
|
|
21
|
+
default: 'true'
|
|
22
|
+
|
|
23
|
+
runs:
|
|
24
|
+
using: composite
|
|
25
|
+
steps:
|
|
26
|
+
- name: Set up Python
|
|
27
|
+
uses: actions/setup-python@v5
|
|
28
|
+
with:
|
|
29
|
+
python-version: ${{ inputs.python-version }}
|
|
30
|
+
|
|
31
|
+
- name: Install agentkit-cli and quartet tools
|
|
32
|
+
shell: bash
|
|
33
|
+
run: |
|
|
34
|
+
pip install --upgrade pip
|
|
35
|
+
pip install agentkit-cli agentmd agentlint coderace agentreflect
|
|
36
|
+
|
|
37
|
+
- name: Run agentkit doctor
|
|
38
|
+
shell: bash
|
|
39
|
+
run: agentkit doctor
|
|
40
|
+
|
|
41
|
+
- name: Run agentkit pipeline
|
|
42
|
+
shell: bash
|
|
43
|
+
run: |
|
|
44
|
+
SKIP_ARGS=""
|
|
45
|
+
if [ -n "${{ inputs.skip }}" ]; then
|
|
46
|
+
for step in $(echo "${{ inputs.skip }}" | tr ',' ' '); do
|
|
47
|
+
SKIP_ARGS="$SKIP_ARGS --skip $step"
|
|
48
|
+
done
|
|
49
|
+
fi
|
|
50
|
+
BENCH_FLAG=""
|
|
51
|
+
if [ "${{ inputs.benchmark }}" = "true" ]; then
|
|
52
|
+
BENCH_FLAG="--benchmark"
|
|
53
|
+
fi
|
|
54
|
+
agentkit run $SKIP_ARGS $BENCH_FLAG --json
|
|
55
|
+
|
|
56
|
+
- name: Check lint result
|
|
57
|
+
if: inputs.fail-on-lint == 'true'
|
|
58
|
+
shell: bash
|
|
59
|
+
run: |
|
|
60
|
+
echo "Lint check enforced (fail-on-lint=true). Pipeline exit code already propagated above."
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
"""agentkit doctor command — diagnose quartet tools installation."""
|
|
2
|
+
from __future__ import annotations
|
|
3
|
+
|
|
4
|
+
import json
|
|
5
|
+
import sys
|
|
6
|
+
from typing import Optional
|
|
7
|
+
|
|
8
|
+
import typer
|
|
9
|
+
from rich.console import Console
|
|
10
|
+
from rich.table import Table
|
|
11
|
+
|
|
12
|
+
from agentkit_cli import __version__
|
|
13
|
+
from agentkit_cli.tools import QUARTET_TOOLS, INSTALL_HINTS, is_installed, get_version
|
|
14
|
+
|
|
15
|
+
console = Console()
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
def check_tool(name: str) -> dict:
|
|
19
|
+
"""Check a single tool and return status dict."""
|
|
20
|
+
installed = is_installed(name)
|
|
21
|
+
version = get_version(name) if installed else None
|
|
22
|
+
return {
|
|
23
|
+
"name": name,
|
|
24
|
+
"installed": installed,
|
|
25
|
+
"version": version or "NOT FOUND",
|
|
26
|
+
"install_hint": INSTALL_HINTS.get(name, f"pip install {name}"),
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
def doctor_command(json_output: bool = False) -> None:
|
|
31
|
+
"""Run doctor checks on all quartet tools."""
|
|
32
|
+
results = {tool: check_tool(tool) for tool in QUARTET_TOOLS}
|
|
33
|
+
all_found = all(r["installed"] for r in results.values())
|
|
34
|
+
|
|
35
|
+
if json_output:
|
|
36
|
+
out = {name: (r["version"] if r["installed"] else "NOT FOUND") for name, r in results.items()}
|
|
37
|
+
out["agentkit-cli"] = __version__
|
|
38
|
+
print(json.dumps(out, indent=2))
|
|
39
|
+
if not all_found:
|
|
40
|
+
raise typer.Exit(code=1)
|
|
41
|
+
return
|
|
42
|
+
|
|
43
|
+
# Rich table output
|
|
44
|
+
console.print(f"\n[bold]agentkit doctor[/bold] — agentkit-cli v{__version__}\n")
|
|
45
|
+
|
|
46
|
+
table = Table(show_header=True)
|
|
47
|
+
table.add_column("Tool", style="bold")
|
|
48
|
+
table.add_column("Status")
|
|
49
|
+
table.add_column("Version")
|
|
50
|
+
table.add_column("Install Command")
|
|
51
|
+
|
|
52
|
+
for name, r in results.items():
|
|
53
|
+
if r["installed"]:
|
|
54
|
+
status = "[green]✓ installed[/green]"
|
|
55
|
+
version = r["version"]
|
|
56
|
+
hint = ""
|
|
57
|
+
else:
|
|
58
|
+
status = "[red]✗ missing[/red]"
|
|
59
|
+
version = "NOT FOUND"
|
|
60
|
+
hint = r["install_hint"]
|
|
61
|
+
table.add_row(name, status, version, hint)
|
|
62
|
+
|
|
63
|
+
# Also show self
|
|
64
|
+
table.add_row("agentkit-cli", "[green]✓ installed[/green]", __version__, "")
|
|
65
|
+
|
|
66
|
+
console.print(table)
|
|
67
|
+
|
|
68
|
+
if all_found:
|
|
69
|
+
console.print("\n[green]All tools installed.[/green]\n")
|
|
70
|
+
else:
|
|
71
|
+
missing = [n for n, r in results.items() if not r["installed"]]
|
|
72
|
+
console.print(f"\n[red]Missing tools: {', '.join(missing)}[/red]\n")
|
|
73
|
+
raise typer.Exit(code=1)
|
|
@@ -120,41 +120,69 @@ def run_command(
|
|
|
120
120
|
else:
|
|
121
121
|
results.append({"step": "reflect", "tool": "agentreflect", "status": "skipped", "reason": "user skipped", "duration": 0.0})
|
|
122
122
|
|
|
123
|
-
#
|
|
124
|
-
|
|
123
|
+
# Build summary counts
|
|
124
|
+
passed_count = sum(1 for r in results if r.get("status") == "pass")
|
|
125
|
+
failed_count = sum(1 for r in results if r.get("status") == "fail")
|
|
126
|
+
skipped_count = sum(1 for r in results if r.get("status") in ("skipped", "error"))
|
|
127
|
+
total_count = len(results)
|
|
128
|
+
|
|
129
|
+
# Display summary table
|
|
130
|
+
STATUS_SYMBOLS = {
|
|
131
|
+
"pass": ("✓ PASS", "green"),
|
|
132
|
+
"fail": ("✗ FAIL", "red"),
|
|
133
|
+
"skipped": ("⊘ SKIPPED", "yellow"),
|
|
134
|
+
"error": ("✗ ERROR", "red"),
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
table = Table(title="Pipeline Summary", show_header=True)
|
|
125
138
|
table.add_column("Step", style="bold")
|
|
126
|
-
table.add_column("Tool")
|
|
127
139
|
table.add_column("Status")
|
|
128
140
|
table.add_column("Duration")
|
|
129
|
-
table.add_column("Notes", max_width=
|
|
130
|
-
|
|
131
|
-
status_colors = {"pass": "green", "fail": "red", "skipped": "yellow", "error": "red"}
|
|
141
|
+
table.add_column("Notes", max_width=60)
|
|
132
142
|
|
|
133
143
|
for r in results:
|
|
134
144
|
status = r.get("status", "unknown")
|
|
135
|
-
color =
|
|
136
|
-
|
|
145
|
+
symbol, color = STATUS_SYMBOLS.get(status, (status, "white"))
|
|
146
|
+
duration_s = r.get("duration", 0.0) or 0.0
|
|
147
|
+
duration = f"{duration_s:.2f}s" if duration_s else ""
|
|
137
148
|
note = r.get("reason", "") or (r.get("output", "")[:60] if r.get("output") else "")
|
|
138
149
|
table.add_row(
|
|
139
150
|
r["step"],
|
|
140
|
-
|
|
141
|
-
f"[{color}]{status}[/{color}]",
|
|
151
|
+
f"[{color}]{symbol}[/{color}]",
|
|
142
152
|
duration,
|
|
143
153
|
note,
|
|
144
154
|
)
|
|
145
155
|
|
|
146
156
|
console.print()
|
|
147
157
|
console.print(table)
|
|
158
|
+
console.print(f"\n[bold]{passed_count}/{total_count} steps passed[/bold]")
|
|
148
159
|
|
|
149
160
|
# Save last run
|
|
161
|
+
step_summary = [
|
|
162
|
+
{
|
|
163
|
+
"step": r["step"],
|
|
164
|
+
"status": r.get("status", "unknown"),
|
|
165
|
+
"duration": r.get("duration", 0.0),
|
|
166
|
+
"notes": r.get("reason", "") or (r.get("output", "")[:60] if r.get("output") else ""),
|
|
167
|
+
}
|
|
168
|
+
for r in results
|
|
169
|
+
]
|
|
150
170
|
summary = {
|
|
151
171
|
"timestamp": datetime.now(timezone.utc).isoformat(),
|
|
152
172
|
"project": cwd_str,
|
|
153
173
|
"steps": results,
|
|
154
|
-
"
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
174
|
+
"summary": {
|
|
175
|
+
"steps": step_summary,
|
|
176
|
+
"total": total_count,
|
|
177
|
+
"passed": passed_count,
|
|
178
|
+
"failed": failed_count,
|
|
179
|
+
"skipped": skipped_count,
|
|
180
|
+
"result": "pass" if failed_count == 0 else "fail",
|
|
181
|
+
},
|
|
182
|
+
"total": total_count,
|
|
183
|
+
"passed": passed_count,
|
|
184
|
+
"failed": failed_count,
|
|
185
|
+
"skipped": skipped_count,
|
|
158
186
|
}
|
|
159
187
|
try:
|
|
160
188
|
save_last_run(summary, root)
|
|
@@ -162,12 +190,11 @@ def run_command(
|
|
|
162
190
|
pass
|
|
163
191
|
|
|
164
192
|
if json_output:
|
|
165
|
-
console.print("\n[bold]JSON Output:[/bold]")
|
|
166
193
|
print(json.dumps(summary, indent=2))
|
|
167
194
|
|
|
168
195
|
# Final status
|
|
169
|
-
if
|
|
170
|
-
console.print(f"\n[red]Pipeline completed with {
|
|
196
|
+
if failed_count > 0:
|
|
197
|
+
console.print(f"\n[red]Pipeline completed with {failed_count} failure(s).[/red]")
|
|
171
198
|
raise typer.Exit(code=1)
|
|
172
199
|
else:
|
|
173
|
-
console.print(f"\n[green]Pipeline complete.[/green] {
|
|
200
|
+
console.print(f"\n[green]Pipeline complete.[/green] {passed_count} passed, {skipped_count} skipped.\n")
|
|
@@ -8,6 +8,7 @@ from pathlib import Path
|
|
|
8
8
|
from agentkit_cli.commands.init_cmd import init_command
|
|
9
9
|
from agentkit_cli.commands.run_cmd import run_command
|
|
10
10
|
from agentkit_cli.commands.status_cmd import status_command
|
|
11
|
+
from agentkit_cli.commands.doctor_cmd import doctor_command
|
|
11
12
|
|
|
12
13
|
app = typer.Typer(
|
|
13
14
|
name="agentkit",
|
|
@@ -36,6 +37,14 @@ def run(
|
|
|
36
37
|
run_command(path=path, skip=skip, benchmark=benchmark, json_output=json_output, notes=notes)
|
|
37
38
|
|
|
38
39
|
|
|
40
|
+
@app.command("doctor")
|
|
41
|
+
def doctor(
|
|
42
|
+
json_output: bool = typer.Option(False, "--json", help="Emit results as JSON"),
|
|
43
|
+
) -> None:
|
|
44
|
+
"""Diagnose whether all quartet tools are installed and functional."""
|
|
45
|
+
doctor_command(json_output=json_output)
|
|
46
|
+
|
|
47
|
+
|
|
39
48
|
@app.command("status")
|
|
40
49
|
def status(
|
|
41
50
|
path: Optional[Path] = typer.Option(None, "--path", "-p", help="Project directory"),
|
|
@@ -0,0 +1,137 @@
|
|
|
1
|
+
# All-Day Build Contract: agentkit-cli v0.2.0 — doctor + GitHub Action
|
|
2
|
+
|
|
3
|
+
Status: In Progress
|
|
4
|
+
Date: 2026-03-12
|
|
5
|
+
Owner: Codex execution pass
|
|
6
|
+
Repo: ~/repos/agentkit-cli/
|
|
7
|
+
Scope type: Deliverable-gated (no hour promises)
|
|
8
|
+
|
|
9
|
+
## 1. Objective
|
|
10
|
+
|
|
11
|
+
Add three features to agentkit-cli that make it production-ready for CI/CD and easier to diagnose:
|
|
12
|
+
|
|
13
|
+
1. `agentkit doctor` — diagnose whether all quartet tools are installed, their versions, and whether they're runnable
|
|
14
|
+
2. GitHub Action (`action.yml`) — single action to run the full agentkit pipeline in CI
|
|
15
|
+
3. Improved `agentkit run` summary — emit a final Rich summary table showing all step results and pass/fail status
|
|
16
|
+
|
|
17
|
+
This contract is considered complete only when every deliverable and validation gate below is satisfied.
|
|
18
|
+
|
|
19
|
+
## 2. Non-Negotiable Build Rules
|
|
20
|
+
|
|
21
|
+
1. No time-based completion claims.
|
|
22
|
+
2. Completion is allowed only when all checklist items are checked.
|
|
23
|
+
3. Full test suite must pass at the end (all existing 47 tests + new ones).
|
|
24
|
+
4. New features must ship with docs and CHANGELOG updates in the same pass.
|
|
25
|
+
5. CLI outputs must be deterministic and schema-backed where specified.
|
|
26
|
+
6. Never modify files outside the project directory.
|
|
27
|
+
7. Commit after each completed deliverable (not at the end).
|
|
28
|
+
8. If stuck on same issue for 3 attempts, stop and write a blocker report.
|
|
29
|
+
9. Do NOT refactor, restyle, or "improve" code outside the deliverables.
|
|
30
|
+
10. Read existing tests and docs before writing new code.
|
|
31
|
+
|
|
32
|
+
## 3. Feature Deliverables
|
|
33
|
+
|
|
34
|
+
### D1. `agentkit doctor` command
|
|
35
|
+
|
|
36
|
+
A diagnostic command that checks whether the quartet tools are installed and functional.
|
|
37
|
+
|
|
38
|
+
Required behavior:
|
|
39
|
+
- Check if `coderace`, `agentmd`, `agentlint`, `agentreflect` are on PATH (via `shutil.which` or subprocess `--version`)
|
|
40
|
+
- For each tool: show installed version or "NOT FOUND"
|
|
41
|
+
- Exit code 0 if all found, exit code 1 if any missing
|
|
42
|
+
- `--json` flag outputs `{"coderace": "1.9.0", "agentmd": "0.6.0", ...}`
|
|
43
|
+
- Rich table output (name | status | version | install command)
|
|
44
|
+
- Check agentkit-cli's own version too
|
|
45
|
+
|
|
46
|
+
Required files:
|
|
47
|
+
- `agentkit_cli/commands/doctor_cmd.py` — implementation
|
|
48
|
+
- Updated `agentkit_cli/main.py` — register doctor command
|
|
49
|
+
- `tests/test_doctor.py` — unit tests (mock subprocess calls)
|
|
50
|
+
|
|
51
|
+
Checklist:
|
|
52
|
+
- [ ] doctor_cmd.py with check_tool() helper
|
|
53
|
+
- [ ] Rich table with ✓/✗ per tool
|
|
54
|
+
- [ ] --json flag
|
|
55
|
+
- [ ] exit 1 on missing tools
|
|
56
|
+
- [ ] tests/test_doctor.py with mocked subprocess (10+ tests)
|
|
57
|
+
- [ ] doctor registered in main.py
|
|
58
|
+
|
|
59
|
+
### D2. GitHub Action (action.yml)
|
|
60
|
+
|
|
61
|
+
A composite GitHub Action that runs `agentkit run` on a repo in CI.
|
|
62
|
+
|
|
63
|
+
Required files:
|
|
64
|
+
- `action.yml` — composite action
|
|
65
|
+
- `.github/workflows/examples/agentkit-pipeline.yml` — example workflow
|
|
66
|
+
|
|
67
|
+
action.yml inputs:
|
|
68
|
+
- `skip` (optional): comma-separated steps to skip (generate, lint, benchmark, reflect)
|
|
69
|
+
- `benchmark` (optional, default false): enable benchmark step
|
|
70
|
+
- `python-version` (optional, default 3.12): Python version
|
|
71
|
+
- `fail-on-lint` (optional, default true): exit 1 on agentlint failures
|
|
72
|
+
|
|
73
|
+
The action should:
|
|
74
|
+
1. Set up Python
|
|
75
|
+
2. Install agentkit-cli + quartet tools
|
|
76
|
+
3. Run `agentkit doctor` first (fast health check)
|
|
77
|
+
4. Run `agentkit run` with the given inputs
|
|
78
|
+
5. On failure: output clear error message with which step failed
|
|
79
|
+
|
|
80
|
+
Example workflow should show a realistic GitHub Action usage for a Python repo.
|
|
81
|
+
|
|
82
|
+
Checklist:
|
|
83
|
+
- [ ] action.yml with 4 inputs
|
|
84
|
+
- [ ] .github/workflows/examples/agentkit-pipeline.yml
|
|
85
|
+
- [ ] README updated with "CI Integration" section showing the action
|
|
86
|
+
- [ ] tests/test_action.py — verify action.yml is valid YAML with required keys (5+ tests)
|
|
87
|
+
|
|
88
|
+
### D3. Improved `agentkit run` summary table
|
|
89
|
+
|
|
90
|
+
After all pipeline steps complete, emit a final Rich summary table showing:
|
|
91
|
+
- Step name | Status (✓ PASS / ✗ FAIL / ⊘ SKIPPED) | Duration | Notes
|
|
92
|
+
- Overall line: "X/Y steps passed"
|
|
93
|
+
- If `--json`: include summary in JSON output as `summary` key
|
|
94
|
+
|
|
95
|
+
This replaces/augments the current per-step output with a clean completion view.
|
|
96
|
+
|
|
97
|
+
Required changes:
|
|
98
|
+
- `agentkit_cli/commands/run_cmd.py` — add summary table at end
|
|
99
|
+
- `tests/test_run.py` — add tests for summary table output (10+ new tests)
|
|
100
|
+
|
|
101
|
+
Checklist:
|
|
102
|
+
- [ ] Summary table with step/status/duration/notes columns
|
|
103
|
+
- [ ] --json summary key
|
|
104
|
+
- [ ] Tests for summary output
|
|
105
|
+
|
|
106
|
+
### D4. Version bump, docs, publish
|
|
107
|
+
|
|
108
|
+
- Bump version to 0.2.0 in pyproject.toml and __init__.py
|
|
109
|
+
- CHANGELOG.md entry for v0.2.0 with all three features
|
|
110
|
+
- README updated: add "doctor" to command list, add CI Integration section, update version badge
|
|
111
|
+
- Build and publish to PyPI: `python -m build && twine upload dist/*`
|
|
112
|
+
- Commit + push + tag v0.2.0 on GitHub
|
|
113
|
+
|
|
114
|
+
Checklist:
|
|
115
|
+
- [ ] version = "0.2.0" in pyproject.toml
|
|
116
|
+
- [ ] __version__ = "0.2.0" in __init__.py
|
|
117
|
+
- [ ] CHANGELOG.md updated
|
|
118
|
+
- [ ] README doctor + CI sections added
|
|
119
|
+
- [ ] PyPI published
|
|
120
|
+
- [ ] git tag v0.2.0 pushed
|
|
121
|
+
|
|
122
|
+
## 4. Test Requirements
|
|
123
|
+
|
|
124
|
+
- [ ] All existing 47 tests still pass
|
|
125
|
+
- [ ] New tests: 10+ for doctor, 5+ for action YAML, 10+ for run summary = 25+ new
|
|
126
|
+
- [ ] Total target: 70+ tests
|
|
127
|
+
|
|
128
|
+
## 5. Reports
|
|
129
|
+
|
|
130
|
+
- Write a BUILD-REPORT.md in ~/repos/agentkit-cli/ at the end
|
|
131
|
+
- Include: what was built, test counts, PyPI URL, any issues encountered
|
|
132
|
+
|
|
133
|
+
## 6. Stop Conditions
|
|
134
|
+
|
|
135
|
+
- All deliverables checked and all tests passing → DONE, write BUILD-REPORT.md
|
|
136
|
+
- 3 consecutive failed attempts on same issue → STOP, write blocker report to BUILD-REPORT.md
|
|
137
|
+
- Scope creep detected → STOP, report in BUILD-REPORT.md
|
|
@@ -4,7 +4,7 @@ build-backend = "hatchling.build"
|
|
|
4
4
|
|
|
5
5
|
[project]
|
|
6
6
|
name = "agentkit-cli"
|
|
7
|
-
version = "0.
|
|
7
|
+
version = "0.2.0"
|
|
8
8
|
description = "Unified CLI for the Agent Quality Toolkit (agentmd, coderace, agentlint, agentreflect)"
|
|
9
9
|
readme = "README.md"
|
|
10
10
|
license = {text = "MIT"}
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
"""Tests for action.yml GitHub Action definition."""
|
|
2
|
+
from __future__ import annotations
|
|
3
|
+
|
|
4
|
+
import yaml
|
|
5
|
+
from pathlib import Path
|
|
6
|
+
|
|
7
|
+
ACTION_PATH = Path(__file__).parent.parent / "action.yml"
|
|
8
|
+
WORKFLOW_PATH = Path(__file__).parent.parent / ".github/workflows/examples/agentkit-pipeline.yml"
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
def _load_action():
|
|
12
|
+
with open(ACTION_PATH) as f:
|
|
13
|
+
return yaml.safe_load(f)
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
def _load_workflow():
|
|
17
|
+
with open(WORKFLOW_PATH) as f:
|
|
18
|
+
return yaml.safe_load(f)
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
def test_action_yml_exists():
|
|
22
|
+
assert ACTION_PATH.exists()
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
def test_action_has_required_inputs():
|
|
26
|
+
data = _load_action()
|
|
27
|
+
inputs = data["inputs"]
|
|
28
|
+
assert "skip" in inputs
|
|
29
|
+
assert "benchmark" in inputs
|
|
30
|
+
assert "python-version" in inputs
|
|
31
|
+
assert "fail-on-lint" in inputs
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
def test_action_is_composite():
|
|
35
|
+
data = _load_action()
|
|
36
|
+
assert data["runs"]["using"] == "composite"
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
def test_action_has_steps():
|
|
40
|
+
data = _load_action()
|
|
41
|
+
steps = data["runs"]["steps"]
|
|
42
|
+
assert len(steps) >= 4
|
|
43
|
+
|
|
44
|
+
|
|
45
|
+
def test_action_has_name_and_description():
|
|
46
|
+
data = _load_action()
|
|
47
|
+
assert "name" in data
|
|
48
|
+
assert "description" in data
|
|
49
|
+
|
|
50
|
+
|
|
51
|
+
def test_action_default_python_version():
|
|
52
|
+
data = _load_action()
|
|
53
|
+
assert data["inputs"]["python-version"]["default"] == "3.12"
|
|
54
|
+
|
|
55
|
+
|
|
56
|
+
def test_action_benchmark_default_false():
|
|
57
|
+
data = _load_action()
|
|
58
|
+
assert data["inputs"]["benchmark"]["default"] == "false"
|
|
59
|
+
|
|
60
|
+
|
|
61
|
+
def test_action_fail_on_lint_default_true():
|
|
62
|
+
data = _load_action()
|
|
63
|
+
assert data["inputs"]["fail-on-lint"]["default"] == "true"
|
|
64
|
+
|
|
65
|
+
|
|
66
|
+
def test_example_workflow_exists():
|
|
67
|
+
assert WORKFLOW_PATH.exists()
|
|
68
|
+
|
|
69
|
+
|
|
70
|
+
def test_example_workflow_valid_yaml():
|
|
71
|
+
data = _load_workflow()
|
|
72
|
+
assert "jobs" in data
|
|
73
|
+
# 'on' is parsed as True by PyYAML (YAML 1.1 bool quirk)
|
|
74
|
+
assert "jobs" in data and len(data) >= 2
|
|
@@ -0,0 +1,129 @@
|
|
|
1
|
+
"""Tests for agentkit doctor command."""
|
|
2
|
+
from __future__ import annotations
|
|
3
|
+
|
|
4
|
+
import json
|
|
5
|
+
import pytest
|
|
6
|
+
from typer.testing import CliRunner
|
|
7
|
+
from unittest.mock import patch, MagicMock
|
|
8
|
+
|
|
9
|
+
from agentkit_cli.main import app
|
|
10
|
+
from agentkit_cli.commands.doctor_cmd import check_tool
|
|
11
|
+
|
|
12
|
+
runner = CliRunner()
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
# --- check_tool unit tests ---
|
|
16
|
+
|
|
17
|
+
def test_check_tool_installed():
|
|
18
|
+
with patch("agentkit_cli.commands.doctor_cmd.is_installed", return_value=True), \
|
|
19
|
+
patch("agentkit_cli.commands.doctor_cmd.get_version", return_value="1.0.0"):
|
|
20
|
+
r = check_tool("coderace")
|
|
21
|
+
assert r["installed"] is True
|
|
22
|
+
assert r["version"] == "1.0.0"
|
|
23
|
+
assert r["name"] == "coderace"
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
def test_check_tool_not_installed():
|
|
27
|
+
with patch("agentkit_cli.commands.doctor_cmd.is_installed", return_value=False):
|
|
28
|
+
r = check_tool("coderace")
|
|
29
|
+
assert r["installed"] is False
|
|
30
|
+
assert r["version"] == "NOT FOUND"
|
|
31
|
+
assert "pip install" in r["install_hint"]
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
def test_check_tool_installed_no_version():
|
|
35
|
+
with patch("agentkit_cli.commands.doctor_cmd.is_installed", return_value=True), \
|
|
36
|
+
patch("agentkit_cli.commands.doctor_cmd.get_version", return_value=None):
|
|
37
|
+
r = check_tool("agentmd")
|
|
38
|
+
assert r["installed"] is True
|
|
39
|
+
assert r["version"] == "NOT FOUND"
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
def test_check_tool_install_hint_present():
|
|
43
|
+
with patch("agentkit_cli.commands.doctor_cmd.is_installed", return_value=False):
|
|
44
|
+
r = check_tool("agentmd")
|
|
45
|
+
assert r["install_hint"] == "pip install agentmd"
|
|
46
|
+
|
|
47
|
+
|
|
48
|
+
# --- CLI integration tests ---
|
|
49
|
+
|
|
50
|
+
def _all_installed(tool):
|
|
51
|
+
return True
|
|
52
|
+
|
|
53
|
+
def _none_installed(tool):
|
|
54
|
+
return False
|
|
55
|
+
|
|
56
|
+
|
|
57
|
+
def test_doctor_all_installed_exit_0():
|
|
58
|
+
with patch("agentkit_cli.commands.doctor_cmd.is_installed", side_effect=_all_installed), \
|
|
59
|
+
patch("agentkit_cli.commands.doctor_cmd.get_version", return_value="1.0.0"):
|
|
60
|
+
result = runner.invoke(app, ["doctor"])
|
|
61
|
+
assert result.exit_code == 0
|
|
62
|
+
|
|
63
|
+
|
|
64
|
+
def test_doctor_missing_tool_exit_1():
|
|
65
|
+
with patch("agentkit_cli.commands.doctor_cmd.is_installed", return_value=False):
|
|
66
|
+
result = runner.invoke(app, ["doctor"])
|
|
67
|
+
assert result.exit_code == 1
|
|
68
|
+
|
|
69
|
+
|
|
70
|
+
def test_doctor_shows_table_output():
|
|
71
|
+
with patch("agentkit_cli.commands.doctor_cmd.is_installed", return_value=True), \
|
|
72
|
+
patch("agentkit_cli.commands.doctor_cmd.get_version", return_value="0.5.0"):
|
|
73
|
+
result = runner.invoke(app, ["doctor"])
|
|
74
|
+
assert "coderace" in result.output
|
|
75
|
+
assert "agentmd" in result.output
|
|
76
|
+
assert "agentlint" in result.output
|
|
77
|
+
assert "agentreflect" in result.output
|
|
78
|
+
|
|
79
|
+
|
|
80
|
+
def test_doctor_json_all_installed():
|
|
81
|
+
with patch("agentkit_cli.commands.doctor_cmd.is_installed", side_effect=_all_installed), \
|
|
82
|
+
patch("agentkit_cli.commands.doctor_cmd.get_version", return_value="2.0.0"):
|
|
83
|
+
result = runner.invoke(app, ["doctor", "--json"])
|
|
84
|
+
assert result.exit_code == 0
|
|
85
|
+
data = json.loads(result.output)
|
|
86
|
+
assert "coderace" in data
|
|
87
|
+
assert "agentmd" in data
|
|
88
|
+
assert "agentkit-cli" in data
|
|
89
|
+
|
|
90
|
+
|
|
91
|
+
def test_doctor_json_missing_exit_1():
|
|
92
|
+
with patch("agentkit_cli.commands.doctor_cmd.is_installed", return_value=False):
|
|
93
|
+
result = runner.invoke(app, ["doctor", "--json"])
|
|
94
|
+
assert result.exit_code == 1
|
|
95
|
+
data = json.loads(result.output)
|
|
96
|
+
assert data["coderace"] == "NOT FOUND"
|
|
97
|
+
|
|
98
|
+
|
|
99
|
+
def test_doctor_json_partial_install():
|
|
100
|
+
def partial(tool):
|
|
101
|
+
return tool in ("coderace", "agentmd")
|
|
102
|
+
|
|
103
|
+
with patch("agentkit_cli.commands.doctor_cmd.is_installed", side_effect=partial), \
|
|
104
|
+
patch("agentkit_cli.commands.doctor_cmd.get_version", return_value="1.0.0"):
|
|
105
|
+
result = runner.invoke(app, ["doctor", "--json"])
|
|
106
|
+
assert result.exit_code == 1
|
|
107
|
+
data = json.loads(result.output)
|
|
108
|
+
assert data["coderace"] == "1.0.0"
|
|
109
|
+
assert data["agentlint"] == "NOT FOUND"
|
|
110
|
+
|
|
111
|
+
|
|
112
|
+
def test_doctor_shows_missing_message():
|
|
113
|
+
with patch("agentkit_cli.commands.doctor_cmd.is_installed", return_value=False):
|
|
114
|
+
result = runner.invoke(app, ["doctor"])
|
|
115
|
+
assert "missing" in result.output.lower() or "NOT FOUND" in result.output
|
|
116
|
+
|
|
117
|
+
|
|
118
|
+
def test_doctor_shows_installed_checkmark():
|
|
119
|
+
with patch("agentkit_cli.commands.doctor_cmd.is_installed", return_value=True), \
|
|
120
|
+
patch("agentkit_cli.commands.doctor_cmd.get_version", return_value="1.0.0"):
|
|
121
|
+
result = runner.invoke(app, ["doctor"])
|
|
122
|
+
assert "installed" in result.output
|
|
123
|
+
|
|
124
|
+
|
|
125
|
+
def test_doctor_shows_agentkit_cli_version():
|
|
126
|
+
with patch("agentkit_cli.commands.doctor_cmd.is_installed", return_value=True), \
|
|
127
|
+
patch("agentkit_cli.commands.doctor_cmd.get_version", return_value="1.0.0"):
|
|
128
|
+
result = runner.invoke(app, ["doctor"])
|
|
129
|
+
assert "agentkit-cli" in result.output
|
|
@@ -146,3 +146,123 @@ def test_run_passing_steps_exit_zero(tmp_path):
|
|
|
146
146
|
patch("agentkit_cli.commands.run_cmd.run_tool", return_value=mock_result):
|
|
147
147
|
result = runner.invoke(app, ["run", "--path", str(tmp_path)])
|
|
148
148
|
assert result.exit_code == 0
|
|
149
|
+
|
|
150
|
+
|
|
151
|
+
# --- D3: Summary table tests ---
|
|
152
|
+
|
|
153
|
+
def test_run_summary_shows_pass_count(tmp_path):
|
|
154
|
+
"""Summary line shows X/Y steps passed."""
|
|
155
|
+
with patch("agentkit_cli.commands.run_cmd.is_installed", return_value=False):
|
|
156
|
+
result = runner.invoke(app, ["run", "--path", str(tmp_path)])
|
|
157
|
+
assert "steps passed" in result.output
|
|
158
|
+
|
|
159
|
+
|
|
160
|
+
def test_run_summary_table_has_columns(tmp_path):
|
|
161
|
+
"""Summary table has Step/Status/Duration/Notes columns."""
|
|
162
|
+
with patch("agentkit_cli.commands.run_cmd.is_installed", return_value=False):
|
|
163
|
+
result = runner.invoke(app, ["run", "--path", str(tmp_path)])
|
|
164
|
+
assert "Step" in result.output
|
|
165
|
+
assert "Status" in result.output
|
|
166
|
+
assert "Duration" in result.output
|
|
167
|
+
assert "Notes" in result.output
|
|
168
|
+
|
|
169
|
+
|
|
170
|
+
def test_run_summary_skipped_symbol(tmp_path):
|
|
171
|
+
"""Skipped steps show ⊘ SKIPPED symbol."""
|
|
172
|
+
with patch("agentkit_cli.commands.run_cmd.is_installed", return_value=False):
|
|
173
|
+
result = runner.invoke(app, ["run", "--path", str(tmp_path)])
|
|
174
|
+
assert "SKIPPED" in result.output
|
|
175
|
+
|
|
176
|
+
|
|
177
|
+
def test_run_summary_pass_symbol(tmp_path):
|
|
178
|
+
"""Passed steps show ✓ PASS symbol."""
|
|
179
|
+
mock_result = MagicMock()
|
|
180
|
+
mock_result.returncode = 0
|
|
181
|
+
mock_result.stdout = "ok"
|
|
182
|
+
mock_result.stderr = ""
|
|
183
|
+
with patch("agentkit_cli.commands.run_cmd.is_installed", return_value=True), \
|
|
184
|
+
patch("agentkit_cli.commands.run_cmd.run_tool", return_value=mock_result):
|
|
185
|
+
result = runner.invoke(app, ["run", "--path", str(tmp_path)])
|
|
186
|
+
assert "PASS" in result.output
|
|
187
|
+
|
|
188
|
+
|
|
189
|
+
def test_run_summary_fail_symbol(tmp_path):
|
|
190
|
+
"""Failed steps show ✗ FAIL symbol."""
|
|
191
|
+
mock_result = MagicMock()
|
|
192
|
+
mock_result.returncode = 1
|
|
193
|
+
mock_result.stdout = "error"
|
|
194
|
+
mock_result.stderr = ""
|
|
195
|
+
with patch("agentkit_cli.commands.run_cmd.is_installed", return_value=True), \
|
|
196
|
+
patch("agentkit_cli.commands.run_cmd.run_tool", return_value=mock_result):
|
|
197
|
+
result = runner.invoke(app, ["run", "--path", str(tmp_path)])
|
|
198
|
+
assert "FAIL" in result.output
|
|
199
|
+
|
|
200
|
+
|
|
201
|
+
def _extract_json(output: str) -> dict:
|
|
202
|
+
"""Extract outermost JSON object from mixed output."""
|
|
203
|
+
start = output.find("{")
|
|
204
|
+
assert start != -1, "No JSON found"
|
|
205
|
+
depth = 0
|
|
206
|
+
for i, ch in enumerate(output[start:], start):
|
|
207
|
+
if ch == "{":
|
|
208
|
+
depth += 1
|
|
209
|
+
elif ch == "}":
|
|
210
|
+
depth -= 1
|
|
211
|
+
if depth == 0:
|
|
212
|
+
return json.loads(output[start:i + 1])
|
|
213
|
+
raise ValueError("Unterminated JSON")
|
|
214
|
+
|
|
215
|
+
|
|
216
|
+
def test_run_json_has_summary_key(tmp_path):
|
|
217
|
+
"""JSON output includes 'summary' key with structured data."""
|
|
218
|
+
with patch("agentkit_cli.commands.run_cmd.is_installed", return_value=False):
|
|
219
|
+
result = runner.invoke(app, ["run", "--path", str(tmp_path), "--json"])
|
|
220
|
+
data = _extract_json(result.output)
|
|
221
|
+
assert "summary" in data
|
|
222
|
+
assert "passed" in data["summary"]
|
|
223
|
+
assert "failed" in data["summary"]
|
|
224
|
+
assert "skipped" in data["summary"]
|
|
225
|
+
assert "total" in data["summary"]
|
|
226
|
+
assert "result" in data["summary"]
|
|
227
|
+
|
|
228
|
+
|
|
229
|
+
def test_run_json_summary_result_pass(tmp_path):
|
|
230
|
+
"""JSON summary result=pass when no failures."""
|
|
231
|
+
with patch("agentkit_cli.commands.run_cmd.is_installed", return_value=False):
|
|
232
|
+
result = runner.invoke(app, ["run", "--path", str(tmp_path), "--json"])
|
|
233
|
+
data = _extract_json(result.output)
|
|
234
|
+
assert data["summary"]["result"] == "pass"
|
|
235
|
+
|
|
236
|
+
|
|
237
|
+
def test_run_json_summary_result_fail(tmp_path):
|
|
238
|
+
"""JSON summary result=fail when a step fails."""
|
|
239
|
+
mock_result = MagicMock()
|
|
240
|
+
mock_result.returncode = 1
|
|
241
|
+
mock_result.stdout = "error"
|
|
242
|
+
mock_result.stderr = ""
|
|
243
|
+
with patch("agentkit_cli.commands.run_cmd.is_installed", return_value=True), \
|
|
244
|
+
patch("agentkit_cli.commands.run_cmd.run_tool", return_value=mock_result):
|
|
245
|
+
result = runner.invoke(app, ["run", "--path", str(tmp_path), "--json"])
|
|
246
|
+
data = _extract_json(result.output)
|
|
247
|
+
assert data["summary"]["result"] == "fail"
|
|
248
|
+
|
|
249
|
+
|
|
250
|
+
def test_run_json_summary_steps_list(tmp_path):
|
|
251
|
+
"""JSON summary.steps is a list with step/status/duration/notes."""
|
|
252
|
+
with patch("agentkit_cli.commands.run_cmd.is_installed", return_value=False):
|
|
253
|
+
result = runner.invoke(app, ["run", "--path", str(tmp_path), "--json"])
|
|
254
|
+
data = _extract_json(result.output)
|
|
255
|
+
steps = data["summary"]["steps"]
|
|
256
|
+
assert isinstance(steps, list)
|
|
257
|
+
assert len(steps) > 0
|
|
258
|
+
first = steps[0]
|
|
259
|
+
assert "step" in first
|
|
260
|
+
assert "status" in first
|
|
261
|
+
assert "duration" in first
|
|
262
|
+
|
|
263
|
+
|
|
264
|
+
def test_run_summary_table_title(tmp_path):
|
|
265
|
+
"""Summary table has 'Pipeline Summary' title."""
|
|
266
|
+
with patch("agentkit_cli.commands.run_cmd.is_installed", return_value=False):
|
|
267
|
+
result = runner.invoke(app, ["run", "--path", str(tmp_path)])
|
|
268
|
+
assert "Pipeline Summary" in result.output
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|