kata-cli 0.9.2__tar.gz → 0.11.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.
- {kata_cli-0.9.2 → kata_cli-0.11.0}/.github/workflows/publish.yml +3 -3
- {kata_cli-0.9.2 → kata_cli-0.11.0}/.gitignore +5 -0
- {kata_cli-0.9.2 → kata_cli-0.11.0}/CHANGELOG.md +56 -0
- {kata_cli-0.9.2 → kata_cli-0.11.0}/CLAUDE.md +24 -38
- {kata_cli-0.9.2 → kata_cli-0.11.0}/PKG-INFO +28 -10
- {kata_cli-0.9.2 → kata_cli-0.11.0}/README.md +27 -9
- {kata_cli-0.9.2 → kata_cli-0.11.0}/antoine/cli/__init__.py +3 -7
- kata_cli-0.11.0/antoine/cli/_commands/log.py +129 -0
- kata_cli-0.11.0/antoine/kata/__init__.py +5 -0
- kata_cli-0.11.0/antoine/kata/log/__init__.py +11 -0
- kata_cli-0.11.0/antoine/kata/log/_args.py +63 -0
- kata_cli-0.11.0/antoine/kata/log/_gc.py +55 -0
- kata_cli-0.11.0/antoine/kata/log/_schema.py +62 -0
- kata_cli-0.11.0/antoine/kata/log/_store.py +56 -0
- kata_cli-0.11.0/docs/kata/log-schema.md +42 -0
- {kata_cli-0.9.2 → kata_cli-0.11.0}/docs/skill-sources.md +6 -2
- kata_cli-0.11.0/docs/superpowers/plans/2026-05-17-kata-log-subsystem.md +1459 -0
- kata_cli-0.11.0/docs/superpowers/specs/2026-05-17-kata-loop-design.md +345 -0
- {kata_cli-0.9.2 → kata_cli-0.11.0}/pyproject.toml +1 -1
- kata_cli-0.11.0/tests/kata/test_log_args.py +63 -0
- kata_cli-0.11.0/tests/kata/test_log_gc.py +72 -0
- kata_cli-0.11.0/tests/kata/test_log_schema.py +91 -0
- kata_cli-0.11.0/tests/kata/test_log_store.py +90 -0
- kata_cli-0.11.0/tests/kata/test_package.py +11 -0
- kata_cli-0.11.0/tests/scripts_eval/fixtures/.gitkeep +0 -0
- kata_cli-0.11.0/tests/test_cli_log_cmd.py +261 -0
- {kata_cli-0.9.2 → kata_cli-0.11.0}/uv.lock +1 -1
- kata_cli-0.9.2/.claude/skills/code-lookup/SKILL.md +0 -100
- kata_cli-0.9.2/.claude/skills/code-lookup/scripts/classify.sh +0 -4
- kata_cli-0.9.2/.claude/skills/code-lookup/scripts/grep.sh +0 -4
- kata_cli-0.9.2/.claude/skills/code-lookup/scripts/recent.sh +0 -4
- kata_cli-0.9.2/.claude/skills/repo-map/SKILL.md +0 -106
- kata_cli-0.9.2/.claude/skills/repo-map/scripts/connections.sh +0 -4
- kata_cli-0.9.2/.claude/skills/repo-map/scripts/graph.sh +0 -4
- kata_cli-0.9.2/.claude/skills/repo-map/scripts/profile.sh +0 -4
- kata_cli-0.9.2/antoine/cli/_commands/classify.py +0 -40
- kata_cli-0.9.2/antoine/cli/_commands/grep.py +0 -44
- kata_cli-0.9.2/antoine/cli/_commands/recent.py +0 -52
- kata_cli-0.9.2/antoine/lookup/__init__.py +0 -25
- kata_cli-0.9.2/antoine/lookup/ast_scope.py +0 -74
- kata_cli-0.9.2/antoine/lookup/classify.py +0 -301
- kata_cli-0.9.2/antoine/lookup/grep_context.py +0 -160
- kata_cli-0.9.2/antoine/lookup/recent_outline.py +0 -304
- kata_cli-0.9.2/antoine/lookup/render.py +0 -41
- kata_cli-0.9.2/antoine/repo/__init__.py +0 -9
- kata_cli-0.9.2/antoine/repo/__main__.py +0 -228
- kata_cli-0.9.2/antoine/repo/config.py +0 -57
- kata_cli-0.9.2/antoine/repo/connections.py +0 -298
- kata_cli-0.9.2/antoine/repo/detect.py +0 -86
- kata_cli-0.9.2/antoine/repo/errors.py +0 -81
- kata_cli-0.9.2/antoine/repo/graph.py +0 -182
- kata_cli-0.9.2/antoine/repo/manifest.py +0 -36
- kata_cli-0.9.2/antoine/repo/profile.py +0 -700
- kata_cli-0.9.2/antoine/repo/render.py +0 -470
- kata_cli-0.9.2/tests/test_ast_scope.py +0 -78
- kata_cli-0.9.2/tests/test_classify.py +0 -319
- kata_cli-0.9.2/tests/test_classify_render.py +0 -80
- kata_cli-0.9.2/tests/test_grep_cmd.py +0 -153
- kata_cli-0.9.2/tests/test_grep_context.py +0 -172
- kata_cli-0.9.2/tests/test_recent_cmd.py +0 -141
- kata_cli-0.9.2/tests/test_recent_outline.py +0 -266
- kata_cli-0.9.2/tests/test_repo_cli.py +0 -160
- kata_cli-0.9.2/tests/test_repo_config.py +0 -76
- kata_cli-0.9.2/tests/test_repo_connections.py +0 -113
- kata_cli-0.9.2/tests/test_repo_detect.py +0 -76
- kata_cli-0.9.2/tests/test_repo_errors.py +0 -52
- kata_cli-0.9.2/tests/test_repo_graph.py +0 -118
- kata_cli-0.9.2/tests/test_repo_manifest.py +0 -62
- kata_cli-0.9.2/tests/test_repo_profile.py +0 -691
- kata_cli-0.9.2/tests/test_repo_render.py +0 -465
- {kata_cli-0.9.2 → kata_cli-0.11.0}/.claude/settings.json +0 -0
- {kata_cli-0.9.2 → kata_cli-0.11.0}/.claude/skills/cicd/SKILL.md +0 -0
- {kata_cli-0.9.2 → kata_cli-0.11.0}/.claude/skills/cicd/scripts/_resolve-nick.sh +0 -0
- {kata_cli-0.9.2 → kata_cli-0.11.0}/.claude/skills/cicd/scripts/portability-lint.sh +0 -0
- {kata_cli-0.9.2 → kata_cli-0.11.0}/.claude/skills/cicd/scripts/pr-reply.sh +0 -0
- {kata_cli-0.9.2 → kata_cli-0.11.0}/.claude/skills/cicd/scripts/pr-status.sh +0 -0
- {kata_cli-0.9.2 → kata_cli-0.11.0}/.claude/skills/cicd/scripts/workflow.sh +0 -0
- {kata_cli-0.9.2 → kata_cli-0.11.0}/.claude/skills/communicate/SKILL.md +0 -0
- {kata_cli-0.9.2 → kata_cli-0.11.0}/.claude/skills/communicate/scripts/fetch-issues.sh +0 -0
- {kata_cli-0.9.2 → kata_cli-0.11.0}/.claude/skills/communicate/scripts/mesh-message.sh +0 -0
- {kata_cli-0.9.2 → kata_cli-0.11.0}/.claude/skills/communicate/scripts/post-comment.sh +0 -0
- {kata_cli-0.9.2 → kata_cli-0.11.0}/.claude/skills/communicate/scripts/post-issue.sh +0 -0
- {kata_cli-0.9.2 → kata_cli-0.11.0}/.claude/skills/communicate/scripts/templates/skill-update-brief.md +0 -0
- {kata_cli-0.9.2 → kata_cli-0.11.0}/.claude/skills/eval/SKILL.md +0 -0
- {kata_cli-0.9.2 → kata_cli-0.11.0}/.claude/skills/run-tests/SKILL.md +0 -0
- {kata_cli-0.9.2 → kata_cli-0.11.0}/.claude/skills/run-tests/scripts/test.sh +0 -0
- {kata_cli-0.9.2 → kata_cli-0.11.0}/.claude/skills/sonarclaude/SKILL.md +0 -0
- {kata_cli-0.9.2 → kata_cli-0.11.0}/.claude/skills/sonarclaude/scripts/sonar.sh +0 -0
- {kata_cli-0.9.2 → kata_cli-0.11.0}/.claude/skills/version-bump/SKILL.md +0 -0
- {kata_cli-0.9.2 → kata_cli-0.11.0}/.claude/skills/version-bump/scripts/bump.py +0 -0
- {kata_cli-0.9.2 → kata_cli-0.11.0}/.claude/skills.local.yaml.example +0 -0
- {kata_cli-0.9.2 → kata_cli-0.11.0}/.flake8 +0 -0
- {kata_cli-0.9.2 → kata_cli-0.11.0}/.github/workflows/security-checks.yml +0 -0
- {kata_cli-0.9.2 → kata_cli-0.11.0}/.github/workflows/tests.yml +0 -0
- {kata_cli-0.9.2 → kata_cli-0.11.0}/.markdownlint-cli2.yaml +0 -0
- {kata_cli-0.9.2 → kata_cli-0.11.0}/.pre-commit-config.yaml +0 -0
- {kata_cli-0.9.2 → kata_cli-0.11.0}/LICENSE +0 -0
- {kata_cli-0.9.2 → kata_cli-0.11.0}/antoine/__init__.py +0 -0
- {kata_cli-0.9.2 → kata_cli-0.11.0}/antoine/__main__.py +0 -0
- {kata_cli-0.9.2 → kata_cli-0.11.0}/antoine/cli/_commands/__init__.py +0 -0
- {kata_cli-0.9.2 → kata_cli-0.11.0}/antoine/cli/_commands/explain.py +0 -0
- {kata_cli-0.9.2 → kata_cli-0.11.0}/antoine/cli/_commands/learn.py +0 -0
- {kata_cli-0.9.2 → kata_cli-0.11.0}/antoine/cli/_commands/whoami.py +0 -0
- {kata_cli-0.9.2 → kata_cli-0.11.0}/antoine/cli/_errors.py +0 -0
- {kata_cli-0.9.2 → kata_cli-0.11.0}/antoine/cli/_output.py +0 -0
- {kata_cli-0.9.2 → kata_cli-0.11.0}/culture.yaml +0 -0
- {kata_cli-0.9.2 → kata_cli-0.11.0}/docs/eval-rounds/2026-05-15-round-01.md +0 -0
- {kata_cli-0.9.2 → kata_cli-0.11.0}/docs/eval-rounds/2026-05-15-smoke-02-examples.md +0 -0
- {kata_cli-0.9.2 → kata_cli-0.11.0}/docs/eval-rounds/2026-05-16-round-02.md +0 -0
- {kata_cli-0.9.2 → kata_cli-0.11.0}/docs/superpowers/plans/2026-05-15-repo-map.md +0 -0
- {kata_cli-0.9.2 → kata_cli-0.11.0}/docs/superpowers/plans/2026-05-15-scripts-eval-harness.md +0 -0
- {kata_cli-0.9.2 → kata_cli-0.11.0}/docs/superpowers/plans/2026-05-16-seer-classify.md +0 -0
- {kata_cli-0.9.2 → kata_cli-0.11.0}/docs/superpowers/specs/2026-05-15-repo-map-design.md +0 -0
- {kata_cli-0.9.2 → kata_cli-0.11.0}/docs/superpowers/specs/2026-05-15-scripts-eval-harness-design.md +0 -0
- {kata_cli-0.9.2 → kata_cli-0.11.0}/docs/superpowers/specs/2026-05-16-seer-classify-design.md +0 -0
- {kata_cli-0.9.2 → kata_cli-0.11.0}/experiments/__init__.py +0 -0
- {kata_cli-0.9.2 → kata_cli-0.11.0}/experiments/scripts_eval/README.md +0 -0
- {kata_cli-0.9.2 → kata_cli-0.11.0}/experiments/scripts_eval/RUNBOOK.md +0 -0
- {kata_cli-0.9.2 → kata_cli-0.11.0}/experiments/scripts_eval/__init__.py +0 -0
- {kata_cli-0.9.2 → kata_cli-0.11.0}/experiments/scripts_eval/_io.py +0 -0
- {kata_cli-0.9.2 → kata_cli-0.11.0}/experiments/scripts_eval/backfill.py +0 -0
- {kata_cli-0.9.2 → kata_cli-0.11.0}/experiments/scripts_eval/corpus.py +0 -0
- {kata_cli-0.9.2 → kata_cli-0.11.0}/experiments/scripts_eval/corpus.yaml +0 -0
- {kata_cli-0.9.2 → kata_cli-0.11.0}/experiments/scripts_eval/hooks/__init__.py +0 -0
- {kata_cli-0.9.2 → kata_cli-0.11.0}/experiments/scripts_eval/hooks/post_tool.py +0 -0
- {kata_cli-0.9.2 → kata_cli-0.11.0}/experiments/scripts_eval/hooks/pre_tool.py +0 -0
- {kata_cli-0.9.2 → kata_cli-0.11.0}/experiments/scripts_eval/judge.py +0 -0
- {kata_cli-0.9.2 → kata_cli-0.11.0}/experiments/scripts_eval/judge_rubric.md +0 -0
- {kata_cli-0.9.2 → kata_cli-0.11.0}/experiments/scripts_eval/manifest.py +0 -0
- {kata_cli-0.9.2 → kata_cli-0.11.0}/experiments/scripts_eval/report.py +0 -0
- {kata_cli-0.9.2 → kata_cli-0.11.0}/experiments/scripts_eval/results/.gitkeep +0 -0
- {kata_cli-0.9.2 → kata_cli-0.11.0}/experiments/scripts_eval/summarize.py +0 -0
- {kata_cli-0.9.2 → kata_cli-0.11.0}/experiments/scripts_eval/switch-arm.sh +0 -0
- {kata_cli-0.9.2 → kata_cli-0.11.0}/experiments/scripts_eval/trial.py +0 -0
- {kata_cli-0.9.2 → kata_cli-0.11.0}/experiments/scripts_eval/validate.py +0 -0
- {kata_cli-0.9.2 → kata_cli-0.11.0}/sonar-project.properties +0 -0
- {kata_cli-0.9.2 → kata_cli-0.11.0}/tests/__init__.py +0 -0
- {kata_cli-0.9.2/tests/scripts_eval → kata_cli-0.11.0/tests/kata}/__init__.py +0 -0
- /kata_cli-0.9.2/tests/scripts_eval/fixtures/.gitkeep → /kata_cli-0.11.0/tests/scripts_eval/__init__.py +0 -0
- {kata_cli-0.9.2 → kata_cli-0.11.0}/tests/scripts_eval/fixtures/corpus_minimal.yaml +0 -0
- {kata_cli-0.9.2 → kata_cli-0.11.0}/tests/scripts_eval/fixtures/sidechain_min.jsonl +0 -0
- {kata_cli-0.9.2 → kata_cli-0.11.0}/tests/scripts_eval/test_backfill.py +0 -0
- {kata_cli-0.9.2 → kata_cli-0.11.0}/tests/scripts_eval/test_corpus.py +0 -0
- {kata_cli-0.9.2 → kata_cli-0.11.0}/tests/scripts_eval/test_hooks_post_tool.py +0 -0
- {kata_cli-0.9.2 → kata_cli-0.11.0}/tests/scripts_eval/test_hooks_pre_tool.py +0 -0
- {kata_cli-0.9.2 → kata_cli-0.11.0}/tests/scripts_eval/test_io.py +0 -0
- {kata_cli-0.9.2 → kata_cli-0.11.0}/tests/scripts_eval/test_judge.py +0 -0
- {kata_cli-0.9.2 → kata_cli-0.11.0}/tests/scripts_eval/test_manifest.py +0 -0
- {kata_cli-0.9.2 → kata_cli-0.11.0}/tests/scripts_eval/test_report.py +0 -0
- {kata_cli-0.9.2 → kata_cli-0.11.0}/tests/scripts_eval/test_summarize.py +0 -0
- {kata_cli-0.9.2 → kata_cli-0.11.0}/tests/scripts_eval/test_trial.py +0 -0
- {kata_cli-0.9.2 → kata_cli-0.11.0}/tests/scripts_eval/test_validate.py +0 -0
- {kata_cli-0.9.2 → kata_cli-0.11.0}/tests/test_cli_chassis.py +0 -0
- {kata_cli-0.9.2 → kata_cli-0.11.0}/tests/test_cli_errors.py +0 -0
- {kata_cli-0.9.2 → kata_cli-0.11.0}/tests/test_cli_output.py +0 -0
- {kata_cli-0.9.2 → kata_cli-0.11.0}/tests/test_cli_stubs.py +0 -0
- {kata_cli-0.9.2 → kata_cli-0.11.0}/tests/test_package.py +0 -0
|
@@ -57,7 +57,7 @@ jobs:
|
|
|
57
57
|
- name: Build and publish each distribution to TestPyPI
|
|
58
58
|
run: |
|
|
59
59
|
set -euo pipefail
|
|
60
|
-
for pkg in antoine-cli kata-cli
|
|
60
|
+
for pkg in antoine-cli kata-cli; do
|
|
61
61
|
echo "::group::TestPyPI publish $pkg"
|
|
62
62
|
# Run the per-package steps in a subshell so set -e failures
|
|
63
63
|
# don't skip the ::endgroup:: marker — keeps Actions logs
|
|
@@ -81,7 +81,7 @@ jobs:
|
|
|
81
81
|
- name: Print install commands
|
|
82
82
|
if: always()
|
|
83
83
|
run: |
|
|
84
|
-
for pkg in antoine-cli kata-cli
|
|
84
|
+
for pkg in antoine-cli kata-cli; do
|
|
85
85
|
echo "::notice::Test with: uv tool install --index-url https://test.pypi.org/simple/ --index-strategy unsafe-best-match $pkg==${DEV_VERSION}"
|
|
86
86
|
done
|
|
87
87
|
|
|
@@ -105,7 +105,7 @@ jobs:
|
|
|
105
105
|
- name: Build and publish each distribution
|
|
106
106
|
run: |
|
|
107
107
|
set -euo pipefail
|
|
108
|
-
for pkg in antoine-cli kata-cli
|
|
108
|
+
for pkg in antoine-cli kata-cli; do
|
|
109
109
|
echo "::group::Publishing $pkg"
|
|
110
110
|
# Run the per-package steps in a subshell so set -e failures
|
|
111
111
|
# don't skip the ::endgroup:: marker — keeps Actions logs
|
|
@@ -232,3 +232,8 @@ experiments/scripts_eval/results/*
|
|
|
232
232
|
|
|
233
233
|
# Claude Code per-machine scheduler lock
|
|
234
234
|
.claude/scheduled_tasks.lock
|
|
235
|
+
|
|
236
|
+
# kata-cli local store — everything under .antoine/ is local-only,
|
|
237
|
+
# EXCEPT katas.toml which is the committed audit ledger.
|
|
238
|
+
.antoine/*
|
|
239
|
+
!.antoine/katas.toml
|
|
@@ -5,6 +5,62 @@ All notable changes to this project will be documented in this file.
|
|
|
5
5
|
Format follows [Keep a Changelog](https://keepachangelog.com/). This project
|
|
6
6
|
adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
|
7
7
|
|
|
8
|
+
## [0.11.0] - 2026-05-17
|
|
9
|
+
|
|
10
|
+
### Removed
|
|
11
|
+
|
|
12
|
+
- `antoine.lookup` engine + `kata classify` / `kata grep` / `kata recent`
|
|
13
|
+
verbs — migrated to `code-lens-cli` v0.10.0.
|
|
14
|
+
- `antoine.repo` engine + `python -m antoine.repo {profile,connections,graph}`
|
|
15
|
+
surface — migrated to `code-lens-cli` v0.10.0.
|
|
16
|
+
- `.claude/skills/code-lookup/` and `.claude/skills/repo-map/` — re-homed
|
|
17
|
+
under `agentculture/code-lens-cli/.claude/skills/`.
|
|
18
|
+
- 16 tests for the migrated surface (`test_ast_scope`, `test_classify*`,
|
|
19
|
+
`test_grep*`, `test_recent*`, `test_repo_*`).
|
|
20
|
+
|
|
21
|
+
### Changed
|
|
22
|
+
|
|
23
|
+
- `docs/skill-sources.md`: `code-lookup` and `repo-map` rows now point
|
|
24
|
+
at `code-lens-cli` as the upstream supplier; added "graduation"
|
|
25
|
+
precedent bullet to the vendoring policy.
|
|
26
|
+
- `CLAUDE.md` "Dispatching subagents" table now names `code-lens`
|
|
27
|
+
verbs (soft dep — install with `uv tool install code-lens-cli`);
|
|
28
|
+
preserved as **evidence** of the first catalog this loop produced
|
|
29
|
+
rather than as a prescription antoine pushes downstream.
|
|
30
|
+
- `README.md`: new "Results of this loop" section pointing at
|
|
31
|
+
`code-lens-cli`; corrected the alt-publish bullet to drop
|
|
32
|
+
`code-lens-cli` (which left the dual-publish loop in 0.10.0).
|
|
33
|
+
|
|
34
|
+
### Migration
|
|
35
|
+
|
|
36
|
+
- `kata classify .` → `code-lens classify .`
|
|
37
|
+
- `kata grep <pattern> .` → `code-lens grep <pattern> .`
|
|
38
|
+
- `kata recent .` → `code-lens recent .`
|
|
39
|
+
- `python -m antoine.repo profile .` → `code-lens profile .`
|
|
40
|
+
|
|
41
|
+
antoine continues to ship the capture/reduce/assess loop
|
|
42
|
+
(`kata log {tail, gc, grep}` plus the forthcoming cells 2–8); the
|
|
43
|
+
inspection verbs were extracted because they are the loop's *result*,
|
|
44
|
+
not its mechanism. See <https://github.com/agentculture/code-lens-cli/issues/2>
|
|
45
|
+
for handover history.
|
|
46
|
+
|
|
47
|
+
## [0.10.0] - 2026-05-17
|
|
48
|
+
|
|
49
|
+
### Added
|
|
50
|
+
|
|
51
|
+
- `kata log {tail, gc, grep}` verbs for inspecting the local capture log
|
|
52
|
+
under `.antoine/log/`.
|
|
53
|
+
- `antoine.kata.log` engine subpackage: `LogEntry` schema, daily-sharded
|
|
54
|
+
`LogStore`, `ArgsSidecar`, TTL `gc` with privacy invariant (args files
|
|
55
|
+
deleted first).
|
|
56
|
+
- `.gitignore` rule: everything under `.antoine/` is local-only except
|
|
57
|
+
`katas.toml` (the future committed ledger; introduced in cell 4).
|
|
58
|
+
- `docs/kata/log-schema.md` documenting the JSONL row format.
|
|
59
|
+
|
|
60
|
+
This is **Cell 1** of the kata-cli capture/reduce/assess loop. See
|
|
61
|
+
`docs/superpowers/specs/2026-05-17-kata-loop-design.md` for the full
|
|
62
|
+
8-cell scope.
|
|
63
|
+
|
|
8
64
|
## [0.9.2] - 2026-05-17
|
|
9
65
|
|
|
10
66
|
### Changed
|
|
@@ -112,45 +112,31 @@ The harness gates the planned redesign of the `learn` / `explain` /
|
|
|
112
112
|
brainstorm** — fed by the eval results, not by the harness's
|
|
113
113
|
existence.
|
|
114
114
|
|
|
115
|
-
## Dispatching subagents —
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
| Task shape |
|
|
115
|
+
## Dispatching subagents — code-lens verbs (first catalog this loop produced)
|
|
116
|
+
|
|
117
|
+
The four task shapes below have 1-call verbs in
|
|
118
|
+
[`code-lens-cli`](https://github.com/agentculture/code-lens-cli) —
|
|
119
|
+
the first published results catalog of running kata's
|
|
120
|
+
capture/reduce/assess loop on a real codebase (this one). Install with
|
|
121
|
+
`uv tool install code-lens-cli`. If installed, prefer the verb; if not,
|
|
122
|
+
the loop will eventually re-surface the N-call pattern and the next
|
|
123
|
+
catalog release will offer a replacement.
|
|
124
|
+
|
|
125
|
+
This is the **evidence** view of code-lens-cli: it is what running the
|
|
126
|
+
loop produces, not a dependency antoine prescribes downstream. The
|
|
127
|
+
verbs lived under `antoine/lookup/` + `antoine/repo/` through 0.10.0;
|
|
128
|
+
0.11.0 migrated them out (see [code-lens-cli#2](https://github.com/agentculture/code-lens-cli/issues/2)).
|
|
129
|
+
|
|
130
|
+
| Task shape | Verb |
|
|
131
131
|
|---|---|
|
|
132
|
-
| "what changed in the last N commits / which functions or classes changed
|
|
133
|
-
| "where is `<pattern>` referenced / find usages with enclosing scope" | `
|
|
134
|
-
| "what kind of project is this — CLI? library? PyPI-published? dockerized?" | `
|
|
135
|
-
| "profile this repo / build-test story / repo overview
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
so a description-shape change on the skill itself does not move adoption
|
|
141
|
-
(0 of 2 models picked up `antoine recent` for a question perfectly tuned for
|
|
142
|
-
it). Round-3 confirmed the parent-agent path: a fresh session loading the
|
|
143
|
-
table delegated *and the subagent invoked the verb directly via the
|
|
144
|
-
injected directive*.
|
|
145
|
-
|
|
146
|
-
**Scope of this rule — empirical:** the table directly governs the parent
|
|
147
|
-
agent's behavior at delegation time and first-person execution time. It
|
|
148
|
-
does **not** reliably propagate to subagents through CLAUDE.md alone —
|
|
149
|
-
round-4 of the smokes (PR #18 commit `171980f`) showed a fresh subagent
|
|
150
|
-
receiving the broadened table as ambient context still defaulted to `git
|
|
151
|
-
log` via Bash (7 calls, no skill use) for a perfectly-shaped question.
|
|
152
|
-
The lever for subagent adoption stays the prompt-body directive injection
|
|
153
|
-
in row 1 above — the table does not change that.
|
|
132
|
+
| "what changed in the last N commits / which functions or classes changed" | `code-lens recent .` |
|
|
133
|
+
| "where is `<pattern>` referenced / find usages with enclosing scope" | `code-lens grep <pattern> .` |
|
|
134
|
+
| "what kind of project is this — CLI? library? PyPI-published? dockerized?" | `code-lens classify .` |
|
|
135
|
+
| "profile this repo / build-test story / repo overview" | `code-lens profile .` |
|
|
136
|
+
|
|
137
|
+
Empirical adoption notes for this table (PR #18 round-2/3/4 smokes —
|
|
138
|
+
why CLAUDE.md is the lever, not skill descriptions) moved with the
|
|
139
|
+
verbs to code-lens-cli's CLAUDE.md.
|
|
154
140
|
|
|
155
141
|
## Workspace Context
|
|
156
142
|
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: kata-cli
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.11.0
|
|
4
4
|
Summary: antoine — codebase lookup and indexing for agent skills (greenfield AgentCulture sibling).
|
|
5
5
|
Project-URL: Homepage, https://github.com/agentculture/antoine
|
|
6
6
|
Project-URL: Issues, https://github.com/agentculture/antoine/issues
|
|
@@ -41,16 +41,19 @@ actually worth the bet, and the recorded results from past rounds.
|
|
|
41
41
|
placeholder stubs. See [`CLAUDE.md`](./CLAUDE.md) for build / test /
|
|
42
42
|
architecture details.
|
|
43
43
|
|
|
44
|
-
- **`kata-cli`
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
(see [`pyproject.toml`](./pyproject.toml)).
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
in [`.github/workflows/`](./.github/workflows/); see
|
|
44
|
+
- **`kata-cli` — alt-published PyPI distribution** carrying the same
|
|
45
|
+
wheel content as `antoine-cli`. Installing either exposes the same
|
|
46
|
+
pair of console scripts — `antoine` and `kata`
|
|
47
|
+
(see [`pyproject.toml`](./pyproject.toml)). `kata-cli` is the
|
|
48
|
+
distribution label users `pip install`, not a command they run. The
|
|
49
|
+
dual-publish loop is defined in
|
|
50
|
+
[`.github/workflows/`](./.github/workflows/); see
|
|
52
51
|
[`CHANGELOG.md`](./CHANGELOG.md) entries for v0.7.0 / v0.7.1 for the
|
|
53
|
-
history of how the
|
|
52
|
+
history of how the distribution names were wired up. (A third name,
|
|
53
|
+
`code-lens-cli`, was published from this repo through v0.9.2; from
|
|
54
|
+
v0.10.0 onward it lives in [its own
|
|
55
|
+
repo](https://github.com/agentculture/code-lens-cli) — see "Results
|
|
56
|
+
of this loop" below.)
|
|
54
57
|
|
|
55
58
|
- **`experiments/scripts_eval/`** — the A/B-test harness for the
|
|
56
59
|
`repo-map` skill (env-var-gated hooks, three-layer scoring, 5-repo
|
|
@@ -69,3 +72,18 @@ actually worth the bet, and the recorded results from past rounds.
|
|
|
69
72
|
*before* consulting the skills catalog, which is why the dispatching
|
|
70
73
|
table lives in the parent agent's instructions rather than in skill
|
|
71
74
|
descriptions.
|
|
75
|
+
|
|
76
|
+
## Results of this loop
|
|
77
|
+
|
|
78
|
+
antoine ships the *tool*. The first published *catalog of results* from
|
|
79
|
+
running its capture/reduce/assess loop is
|
|
80
|
+
[`code-lens-cli`](https://github.com/agentculture/code-lens-cli) —
|
|
81
|
+
four 1-call verbs (`classify` / `recent` / `grep` / `profile`) that
|
|
82
|
+
antoine maintainers identified as recurring N-call patterns and
|
|
83
|
+
packaged into a sibling distribution. Install with
|
|
84
|
+
`uv tool install code-lens-cli`. Most agents will install both.
|
|
85
|
+
|
|
86
|
+
The migration history (antoine 0.10.0 → code-lens-cli 0.10.0,
|
|
87
|
+
2026-05-17) is the first concrete proof that the loop produces shippable
|
|
88
|
+
artifacts. Future cells (2–8) of the loop are designed to make catalogs
|
|
89
|
+
like this one routine rather than artisanal.
|
|
@@ -23,16 +23,19 @@ actually worth the bet, and the recorded results from past rounds.
|
|
|
23
23
|
placeholder stubs. See [`CLAUDE.md`](./CLAUDE.md) for build / test /
|
|
24
24
|
architecture details.
|
|
25
25
|
|
|
26
|
-
- **`kata-cli`
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
(see [`pyproject.toml`](./pyproject.toml)).
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
in [`.github/workflows/`](./.github/workflows/); see
|
|
26
|
+
- **`kata-cli` — alt-published PyPI distribution** carrying the same
|
|
27
|
+
wheel content as `antoine-cli`. Installing either exposes the same
|
|
28
|
+
pair of console scripts — `antoine` and `kata`
|
|
29
|
+
(see [`pyproject.toml`](./pyproject.toml)). `kata-cli` is the
|
|
30
|
+
distribution label users `pip install`, not a command they run. The
|
|
31
|
+
dual-publish loop is defined in
|
|
32
|
+
[`.github/workflows/`](./.github/workflows/); see
|
|
34
33
|
[`CHANGELOG.md`](./CHANGELOG.md) entries for v0.7.0 / v0.7.1 for the
|
|
35
|
-
history of how the
|
|
34
|
+
history of how the distribution names were wired up. (A third name,
|
|
35
|
+
`code-lens-cli`, was published from this repo through v0.9.2; from
|
|
36
|
+
v0.10.0 onward it lives in [its own
|
|
37
|
+
repo](https://github.com/agentculture/code-lens-cli) — see "Results
|
|
38
|
+
of this loop" below.)
|
|
36
39
|
|
|
37
40
|
- **`experiments/scripts_eval/`** — the A/B-test harness for the
|
|
38
41
|
`repo-map` skill (env-var-gated hooks, three-layer scoring, 5-repo
|
|
@@ -51,3 +54,18 @@ actually worth the bet, and the recorded results from past rounds.
|
|
|
51
54
|
*before* consulting the skills catalog, which is why the dispatching
|
|
52
55
|
table lives in the parent agent's instructions rather than in skill
|
|
53
56
|
descriptions.
|
|
57
|
+
|
|
58
|
+
## Results of this loop
|
|
59
|
+
|
|
60
|
+
antoine ships the *tool*. The first published *catalog of results* from
|
|
61
|
+
running its capture/reduce/assess loop is
|
|
62
|
+
[`code-lens-cli`](https://github.com/agentculture/code-lens-cli) —
|
|
63
|
+
four 1-call verbs (`classify` / `recent` / `grep` / `profile`) that
|
|
64
|
+
antoine maintainers identified as recurring N-call patterns and
|
|
65
|
+
packaged into a sibling distribution. Install with
|
|
66
|
+
`uv tool install code-lens-cli`. Most agents will install both.
|
|
67
|
+
|
|
68
|
+
The migration history (antoine 0.10.0 → code-lens-cli 0.10.0,
|
|
69
|
+
2026-05-17) is the first concrete proof that the loop produces shippable
|
|
70
|
+
artifacts. Future cells (2–8) of the loop are designed to make catalogs
|
|
71
|
+
like this one routine rather than artisanal.
|
|
@@ -12,7 +12,7 @@ the raw argv (:func:`main` sets ``_AntoineArgumentParser._json_hint`` before
|
|
|
12
12
|
``parse_args``).
|
|
13
13
|
"""
|
|
14
14
|
|
|
15
|
-
# pylint: disable=duplicate-code # _dispatch
|
|
15
|
+
# pylint: disable=duplicate-code # _dispatch keeps a deliberate pattern shared with sibling CLIs
|
|
16
16
|
|
|
17
17
|
from __future__ import annotations
|
|
18
18
|
|
|
@@ -57,11 +57,9 @@ def _build_parser() -> argparse.ArgumentParser:
|
|
|
57
57
|
sub = parser.add_subparsers(dest="command", parser_class=_AntoineArgumentParser)
|
|
58
58
|
|
|
59
59
|
# pylint: disable=import-outside-toplevel
|
|
60
|
-
from antoine.cli._commands import classify as _classify_cmd
|
|
61
60
|
from antoine.cli._commands import explain as _explain_cmd
|
|
62
|
-
from antoine.cli._commands import grep as _grep_cmd
|
|
63
61
|
from antoine.cli._commands import learn as _learn_cmd
|
|
64
|
-
from antoine.cli._commands import
|
|
62
|
+
from antoine.cli._commands import log as _log_cmd
|
|
65
63
|
from antoine.cli._commands import whoami as _whoami_cmd
|
|
66
64
|
|
|
67
65
|
# pylint: enable=import-outside-toplevel
|
|
@@ -69,9 +67,7 @@ def _build_parser() -> argparse.ArgumentParser:
|
|
|
69
67
|
_learn_cmd.register(sub)
|
|
70
68
|
_explain_cmd.register(sub)
|
|
71
69
|
_whoami_cmd.register(sub)
|
|
72
|
-
|
|
73
|
-
_grep_cmd.register(sub)
|
|
74
|
-
_recent_cmd.register(sub)
|
|
70
|
+
_log_cmd.register(sub)
|
|
75
71
|
|
|
76
72
|
return parser
|
|
77
73
|
|
|
@@ -0,0 +1,129 @@
|
|
|
1
|
+
"""`kata log` verb: parent + tail/gc/grep subcommands."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import argparse
|
|
6
|
+
from collections import deque
|
|
7
|
+
|
|
8
|
+
from antoine.cli._errors import EXIT_ENV_ERROR, EXIT_USER_ERROR, AntoineError
|
|
9
|
+
from antoine.cli._output import emit_result
|
|
10
|
+
from antoine.kata.log._gc import gc as _gc_run
|
|
11
|
+
from antoine.kata.log._store import LogStore
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
def _no_subcommand(args: argparse.Namespace) -> int:
|
|
15
|
+
args._parent_parser.print_help()
|
|
16
|
+
return 0
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
def _handle_grep(args: argparse.Namespace) -> int:
|
|
20
|
+
store = LogStore()
|
|
21
|
+
if not store.root.exists() or not any(store.root.glob("*.jsonl")):
|
|
22
|
+
raise AntoineError(
|
|
23
|
+
code=EXIT_ENV_ERROR,
|
|
24
|
+
message="No capture data found in .antoine/log/. antoine has nothing to grep.",
|
|
25
|
+
remediation=(
|
|
26
|
+
"Run `kata learn` to see how to instrument your agent, "
|
|
27
|
+
"then start a session before grepping the log."
|
|
28
|
+
),
|
|
29
|
+
)
|
|
30
|
+
|
|
31
|
+
needle = args.pattern
|
|
32
|
+
for entry in store.read_all():
|
|
33
|
+
haystack = " ".join(
|
|
34
|
+
v for v in (entry.tool, entry.bash_argv0 or "", entry.agent, entry.session) if v
|
|
35
|
+
)
|
|
36
|
+
if needle in haystack:
|
|
37
|
+
emit_result(entry.to_json_line().rstrip("\n"), json_mode=False)
|
|
38
|
+
return 0
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
def _handle_tail(args: argparse.Namespace) -> int:
|
|
42
|
+
store = LogStore()
|
|
43
|
+
if not store.root.exists() or not any(store.root.glob("*.jsonl")):
|
|
44
|
+
raise AntoineError(
|
|
45
|
+
code=EXIT_ENV_ERROR,
|
|
46
|
+
message=(
|
|
47
|
+
"No capture data found in .antoine/log/. "
|
|
48
|
+
"antoine has no observed tool calls to display."
|
|
49
|
+
),
|
|
50
|
+
remediation=(
|
|
51
|
+
"Run `kata learn` to see how to instrument your agent, "
|
|
52
|
+
"then start a session and try `kata log tail` again."
|
|
53
|
+
),
|
|
54
|
+
)
|
|
55
|
+
|
|
56
|
+
n = max(1, int(args.n))
|
|
57
|
+
last = deque(store.read_all(), maxlen=n)
|
|
58
|
+
for entry in last:
|
|
59
|
+
emit_result(entry.to_json_line().rstrip("\n"), json_mode=False)
|
|
60
|
+
return 0
|
|
61
|
+
|
|
62
|
+
|
|
63
|
+
def _handle_gc(args: argparse.Namespace) -> int:
|
|
64
|
+
if args.ttl_days < 0:
|
|
65
|
+
raise AntoineError(
|
|
66
|
+
code=EXIT_USER_ERROR,
|
|
67
|
+
message=(
|
|
68
|
+
f"--ttl-days must be >= 0 (got {args.ttl_days}). "
|
|
69
|
+
"A negative TTL would classify every log file as stale and "
|
|
70
|
+
"delete the entire log."
|
|
71
|
+
),
|
|
72
|
+
remediation="Re-run with a non-negative value, e.g. `kata log gc --ttl-days 7`.",
|
|
73
|
+
)
|
|
74
|
+
|
|
75
|
+
store = LogStore()
|
|
76
|
+
if not store.root.exists():
|
|
77
|
+
message = "deleted 0 shape files, 0 args files (no log present)"
|
|
78
|
+
else:
|
|
79
|
+
try:
|
|
80
|
+
result = _gc_run(root=store.root, ttl_days=args.ttl_days)
|
|
81
|
+
except PermissionError as exc:
|
|
82
|
+
raise AntoineError(
|
|
83
|
+
code=EXIT_ENV_ERROR,
|
|
84
|
+
message=(
|
|
85
|
+
f"GC could not delete files past TTL: {exc}. "
|
|
86
|
+
"The privacy invariant requires expired data to be removed."
|
|
87
|
+
),
|
|
88
|
+
remediation=(
|
|
89
|
+
"Check filesystem permissions on .antoine/log/ "
|
|
90
|
+
"(needs delete access for the user running antoine), then retry."
|
|
91
|
+
),
|
|
92
|
+
) from exc
|
|
93
|
+
message = (
|
|
94
|
+
f"deleted {len(result.deleted_shape)} shape files, "
|
|
95
|
+
f"{len(result.deleted_args)} args files"
|
|
96
|
+
)
|
|
97
|
+
|
|
98
|
+
emit_result(message, json_mode=False)
|
|
99
|
+
return 0
|
|
100
|
+
|
|
101
|
+
|
|
102
|
+
def register(sub: argparse._SubParsersAction) -> None:
|
|
103
|
+
parser = sub.add_parser(
|
|
104
|
+
"log",
|
|
105
|
+
help="raw access to the local capture log",
|
|
106
|
+
description="Inspect, prune, or search the local kata capture log under ./.antoine/log/.",
|
|
107
|
+
)
|
|
108
|
+
parser.set_defaults(func=_no_subcommand, _parent_parser=parser)
|
|
109
|
+
# Nested parsers MUST share the structured-error wrapper used by the top
|
|
110
|
+
# level — otherwise `kata log tail --bogus` exits 2 via default argparse,
|
|
111
|
+
# bypassing the "antoine never crashes" contract. (Qodo #25 bug 1.)
|
|
112
|
+
subsub = parser.add_subparsers(dest="log_command", parser_class=type(parser))
|
|
113
|
+
|
|
114
|
+
tail = subsub.add_parser("tail", help="print the last N captured entries")
|
|
115
|
+
tail.add_argument("-n", default=10, type=int, help="number of entries (default: 10)")
|
|
116
|
+
tail.set_defaults(func=_handle_tail)
|
|
117
|
+
|
|
118
|
+
gc_p = subsub.add_parser("gc", help="delete capture entries past TTL")
|
|
119
|
+
gc_p.add_argument(
|
|
120
|
+
"--ttl-days",
|
|
121
|
+
type=int,
|
|
122
|
+
default=7,
|
|
123
|
+
help="retention window in days (default: 7)",
|
|
124
|
+
)
|
|
125
|
+
gc_p.set_defaults(func=_handle_gc)
|
|
126
|
+
|
|
127
|
+
grep_p = subsub.add_parser("grep", help="filter log entries by substring")
|
|
128
|
+
grep_p.add_argument("pattern", help="substring matched against tool/bash_argv0/agent/session")
|
|
129
|
+
grep_p.set_defaults(func=_handle_grep)
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
"""Log subsystem: JSONL schema, on-disk store, TTL gc.
|
|
2
|
+
|
|
3
|
+
Layout (under the current working directory):
|
|
4
|
+
|
|
5
|
+
.antoine/log/<YYYY-MM-DD>.jsonl shape index, one line per tool call
|
|
6
|
+
.antoine/log/args/<session>.jsonl raw args sidecar, keyed by row index
|
|
7
|
+
|
|
8
|
+
The shape index is what `suggest`/`assess` read. The args sidecar holds
|
|
9
|
+
raw, privacy-sensitive data and is the first thing the 7-day TTL pass
|
|
10
|
+
deletes.
|
|
11
|
+
"""
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
"""ArgsSidecar — raw args kept separately from the shape index.
|
|
2
|
+
|
|
3
|
+
This is the privacy-sensitive half of the log. TTL gc deletes this
|
|
4
|
+
directory first; the shape index can be retained longer if the user wants
|
|
5
|
+
aggregate stats without raw content.
|
|
6
|
+
|
|
7
|
+
Files live at <root>/args/<session>.jsonl, one JSON object per line.
|
|
8
|
+
"""
|
|
9
|
+
|
|
10
|
+
from __future__ import annotations
|
|
11
|
+
|
|
12
|
+
import json
|
|
13
|
+
import re
|
|
14
|
+
from collections.abc import Iterator
|
|
15
|
+
from pathlib import Path
|
|
16
|
+
from typing import Any
|
|
17
|
+
|
|
18
|
+
# Whitelist: alphanumeric, dot, underscore, hyphen. No path separators,
|
|
19
|
+
# no `..`, no whitespace. Keeps args/<session>.jsonl strictly flat so
|
|
20
|
+
# `kata log gc`'s non-recursive `*.jsonl` scan can't miss a file (the
|
|
21
|
+
# privacy invariant requires expired args to be deleted on schedule).
|
|
22
|
+
# (Qodo #25 bug 2.)
|
|
23
|
+
_SAFE_SESSION_RE = re.compile(r"^[A-Za-z0-9._-]+$")
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
def _validate_session(session: str) -> str:
|
|
27
|
+
# `.` and `..` pass the char-class regex but are path-traversal refs.
|
|
28
|
+
if session in {".", ".."} or not _SAFE_SESSION_RE.fullmatch(session):
|
|
29
|
+
raise ValueError(
|
|
30
|
+
f"unsafe session id {session!r}: must match [A-Za-z0-9._-]+ "
|
|
31
|
+
"and cannot be '.' or '..' (the args sidecar stays flat under "
|
|
32
|
+
"args/<session>.jsonl)"
|
|
33
|
+
)
|
|
34
|
+
return session
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
class ArgsSidecar:
|
|
38
|
+
"""Per-session JSONL store for raw tool args."""
|
|
39
|
+
|
|
40
|
+
def __init__(self, root: Path) -> None:
|
|
41
|
+
self.root = root
|
|
42
|
+
self.args_dir = root / "args"
|
|
43
|
+
|
|
44
|
+
def _file_for(self, session: str) -> Path:
|
|
45
|
+
safe = _validate_session(session)
|
|
46
|
+
return self.args_dir / f"{safe}.jsonl"
|
|
47
|
+
|
|
48
|
+
def append(self, session: str, args: dict[str, Any]) -> None:
|
|
49
|
+
self.args_dir.mkdir(parents=True, exist_ok=True)
|
|
50
|
+
path = self._file_for(session)
|
|
51
|
+
with path.open("a", encoding="utf-8") as fp:
|
|
52
|
+
fp.write(json.dumps(args, ensure_ascii=False, separators=(",", ":")) + "\n")
|
|
53
|
+
|
|
54
|
+
def read(self, session: str) -> Iterator[dict[str, Any]]:
|
|
55
|
+
path = self._file_for(session)
|
|
56
|
+
if not path.exists():
|
|
57
|
+
return
|
|
58
|
+
with path.open("r", encoding="utf-8") as fp:
|
|
59
|
+
for raw in fp:
|
|
60
|
+
stripped = raw.strip()
|
|
61
|
+
if not stripped:
|
|
62
|
+
continue
|
|
63
|
+
yield json.loads(stripped)
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
"""TTL gc — privacy invariant: if files past TTL exist, they MUST be deleted.
|
|
2
|
+
|
|
3
|
+
The order is intentional: raw args first (privacy-sensitive), then the
|
|
4
|
+
shape index. If any unlink raises ``PermissionError``, propagate it
|
|
5
|
+
unchanged so the caller (the ``kata log gc`` verb handler) can translate
|
|
6
|
+
it into an ``AntoineError`` with an actionable Fix line.
|
|
7
|
+
"""
|
|
8
|
+
|
|
9
|
+
from __future__ import annotations
|
|
10
|
+
|
|
11
|
+
import time
|
|
12
|
+
from dataclasses import dataclass, field
|
|
13
|
+
from pathlib import Path
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
@dataclass(frozen=True)
|
|
17
|
+
class GCResult:
|
|
18
|
+
deleted_shape: list[Path] = field(default_factory=list)
|
|
19
|
+
deleted_args: list[Path] = field(default_factory=list)
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
def _stale_files(directory: Path, ttl_seconds: float, now: float) -> list[Path]:
|
|
23
|
+
if not directory.exists():
|
|
24
|
+
return []
|
|
25
|
+
out: list[Path] = []
|
|
26
|
+
for p in sorted(directory.glob("*.jsonl")):
|
|
27
|
+
if (now - p.stat().st_mtime) > ttl_seconds:
|
|
28
|
+
out.append(p)
|
|
29
|
+
return out
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
def gc(*, root: Path, ttl_days: int) -> GCResult:
|
|
33
|
+
"""Delete files older than ``ttl_days`` from ``root`` and ``root/args``.
|
|
34
|
+
|
|
35
|
+
Raises ``ValueError`` on negative ``ttl_days`` — a negative TTL would
|
|
36
|
+
classify every file as stale and wipe the whole log from a single typo,
|
|
37
|
+
which we treat as a programming error, not silently allowed input.
|
|
38
|
+
Raises ``PermissionError`` if any unlink fails — privacy invariant.
|
|
39
|
+
"""
|
|
40
|
+
if ttl_days < 0:
|
|
41
|
+
raise ValueError(f"ttl_days must be >= 0 (got {ttl_days})")
|
|
42
|
+
now = time.time()
|
|
43
|
+
ttl_seconds = ttl_days * 86400.0
|
|
44
|
+
|
|
45
|
+
args_dir = root / "args"
|
|
46
|
+
stale_args = _stale_files(args_dir, ttl_seconds, now)
|
|
47
|
+
stale_shape = _stale_files(root, ttl_seconds, now)
|
|
48
|
+
|
|
49
|
+
# Args first — they hold the raw, privacy-sensitive data.
|
|
50
|
+
for path in stale_args:
|
|
51
|
+
path.unlink()
|
|
52
|
+
for path in stale_shape:
|
|
53
|
+
path.unlink()
|
|
54
|
+
|
|
55
|
+
return GCResult(deleted_shape=stale_shape, deleted_args=stale_args)
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
"""LogEntry — the JSONL row written by an adapter per captured tool call.
|
|
2
|
+
|
|
3
|
+
The schema is documented in:
|
|
4
|
+
docs/kata/log-schema.md (added in a later task in this plan)
|
|
5
|
+
|
|
6
|
+
This module is pure data + serialization; it owns no IO.
|
|
7
|
+
"""
|
|
8
|
+
|
|
9
|
+
from __future__ import annotations
|
|
10
|
+
|
|
11
|
+
import json
|
|
12
|
+
from dataclasses import asdict, dataclass, fields
|
|
13
|
+
from typing import Any
|
|
14
|
+
|
|
15
|
+
_REQUIRED = ("ts", "session", "agent", "tool", "args_digest")
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
@dataclass(frozen=True)
|
|
19
|
+
class LogEntry:
|
|
20
|
+
"""One captured tool call.
|
|
21
|
+
|
|
22
|
+
Required fields (always present): ts, session, agent, tool, args_digest.
|
|
23
|
+
Optional fields (None if the adapter could not provide them):
|
|
24
|
+
bash_argv0, tokens_in, tokens_out, duration_ms.
|
|
25
|
+
"""
|
|
26
|
+
|
|
27
|
+
ts: str
|
|
28
|
+
session: str
|
|
29
|
+
agent: str
|
|
30
|
+
tool: str
|
|
31
|
+
args_digest: str
|
|
32
|
+
bash_argv0: str | None = None
|
|
33
|
+
tokens_in: int | None = None
|
|
34
|
+
tokens_out: int | None = None
|
|
35
|
+
duration_ms: int | None = None
|
|
36
|
+
|
|
37
|
+
def __post_init__(self) -> None:
|
|
38
|
+
if not self.args_digest.startswith("sha256:"):
|
|
39
|
+
raise ValueError(
|
|
40
|
+
"args_digest must start with 'sha256:' " f"(got: {self.args_digest!r})"
|
|
41
|
+
)
|
|
42
|
+
|
|
43
|
+
def to_json_line(self) -> str:
|
|
44
|
+
"""Return a single newline-terminated JSON object."""
|
|
45
|
+
payload = asdict(self)
|
|
46
|
+
return json.dumps(payload, ensure_ascii=False, separators=(",", ":")) + "\n"
|
|
47
|
+
|
|
48
|
+
@classmethod
|
|
49
|
+
def from_json_line(cls, line: str) -> "LogEntry":
|
|
50
|
+
"""Parse one JSONL row. Raises ValueError on missing required fields.
|
|
51
|
+
|
|
52
|
+
Unknown fields in ``payload`` are silently dropped — adapters may emit
|
|
53
|
+
future-evolved schemas, and tolerating that keeps log ingestion working
|
|
54
|
+
instead of TypeError-ing through ``cls(**payload)``. Forward-compat is
|
|
55
|
+
part of the "antoine never crashes" contract.
|
|
56
|
+
"""
|
|
57
|
+
payload: dict[str, Any] = json.loads(line)
|
|
58
|
+
for field_name in _REQUIRED:
|
|
59
|
+
if field_name not in payload:
|
|
60
|
+
raise ValueError(f"missing required field: {field_name!r}")
|
|
61
|
+
known = {f.name for f in fields(cls)}
|
|
62
|
+
return cls(**{k: v for k, v in payload.items() if k in known})
|