agentpack-cli 0.3.13__tar.gz → 0.3.14__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.
- {agentpack_cli-0.3.13 → agentpack_cli-0.3.14}/PKG-INFO +11 -5
- {agentpack_cli-0.3.13 → agentpack_cli-0.3.14}/README.md +10 -4
- {agentpack_cli-0.3.13 → agentpack_cli-0.3.14}/pyproject.toml +1 -1
- {agentpack_cli-0.3.13 → agentpack_cli-0.3.14}/src/agentpack/__init__.py +1 -1
- {agentpack_cli-0.3.13 → agentpack_cli-0.3.14}/src/agentpack/application/pack_service.py +2 -0
- {agentpack_cli-0.3.13 → agentpack_cli-0.3.14}/src/agentpack/commands/benchmark.py +22 -0
- {agentpack_cli-0.3.13 → agentpack_cli-0.3.14}/src/agentpack/commands/dev_check.py +14 -1
- {agentpack_cli-0.3.13 → agentpack_cli-0.3.14}/src/agentpack/commands/init.py +4 -0
- agentpack_cli-0.3.14/src/agentpack/commands/learn.py +179 -0
- {agentpack_cli-0.3.13 → agentpack_cli-0.3.14}/src/agentpack/commands/release_check.py +16 -1
- {agentpack_cli-0.3.13 → agentpack_cli-0.3.14}/src/agentpack/core/config.py +8 -0
- {agentpack_cli-0.3.13 → agentpack_cli-0.3.14}/src/agentpack/learning/__init__.py +4 -0
- agentpack_cli-0.3.14/src/agentpack/learning/feedback.py +171 -0
- agentpack_cli-0.3.14/src/agentpack/learning/lesson_ranker.py +39 -0
- {agentpack_cli-0.3.13 → agentpack_cli-0.3.14}/src/agentpack/learning/models.py +34 -0
- agentpack_cli-0.3.14/src/agentpack/learning/provider.py +52 -0
- agentpack_cli-0.3.14/src/agentpack/learning/renderers.py +235 -0
- agentpack_cli-0.3.14/src/agentpack/learning/skill_map.py +142 -0
- agentpack_cli-0.3.13/src/agentpack/commands/learn.py +0 -101
- agentpack_cli-0.3.13/src/agentpack/learning/feedback.py +0 -22
- agentpack_cli-0.3.13/src/agentpack/learning/renderers.py +0 -99
- agentpack_cli-0.3.13/src/agentpack/learning/skill_map.py +0 -29
- {agentpack_cli-0.3.13 → agentpack_cli-0.3.14}/.gitignore +0 -0
- {agentpack_cli-0.3.13 → agentpack_cli-0.3.14}/LICENSE +0 -0
- {agentpack_cli-0.3.13 → agentpack_cli-0.3.14}/src/agentpack/adapters/__init__.py +0 -0
- {agentpack_cli-0.3.13 → agentpack_cli-0.3.14}/src/agentpack/adapters/antigravity.py +0 -0
- {agentpack_cli-0.3.13 → agentpack_cli-0.3.14}/src/agentpack/adapters/base.py +0 -0
- {agentpack_cli-0.3.13 → agentpack_cli-0.3.14}/src/agentpack/adapters/claude.py +0 -0
- {agentpack_cli-0.3.13 → agentpack_cli-0.3.14}/src/agentpack/adapters/codex.py +0 -0
- {agentpack_cli-0.3.13 → agentpack_cli-0.3.14}/src/agentpack/adapters/cursor.py +0 -0
- {agentpack_cli-0.3.13 → agentpack_cli-0.3.14}/src/agentpack/adapters/detect.py +0 -0
- {agentpack_cli-0.3.13 → agentpack_cli-0.3.14}/src/agentpack/adapters/generic.py +0 -0
- {agentpack_cli-0.3.13 → agentpack_cli-0.3.14}/src/agentpack/adapters/windsurf.py +0 -0
- {agentpack_cli-0.3.13 → agentpack_cli-0.3.14}/src/agentpack/analysis/__init__.py +0 -0
- {agentpack_cli-0.3.13 → agentpack_cli-0.3.14}/src/agentpack/analysis/dependency_graph.py +0 -0
- {agentpack_cli-0.3.13 → agentpack_cli-0.3.14}/src/agentpack/analysis/go_imports.py +0 -0
- {agentpack_cli-0.3.13 → agentpack_cli-0.3.14}/src/agentpack/analysis/java_imports.py +0 -0
- {agentpack_cli-0.3.13 → agentpack_cli-0.3.14}/src/agentpack/analysis/js_ts_imports.py +0 -0
- {agentpack_cli-0.3.13 → agentpack_cli-0.3.14}/src/agentpack/analysis/monorepo.py +0 -0
- {agentpack_cli-0.3.13 → agentpack_cli-0.3.14}/src/agentpack/analysis/naming_signals.py +0 -0
- {agentpack_cli-0.3.13 → agentpack_cli-0.3.14}/src/agentpack/analysis/python_imports.py +0 -0
- {agentpack_cli-0.3.13 → agentpack_cli-0.3.14}/src/agentpack/analysis/ranking.py +0 -0
- {agentpack_cli-0.3.13 → agentpack_cli-0.3.14}/src/agentpack/analysis/repo_map.py +0 -0
- {agentpack_cli-0.3.13 → agentpack_cli-0.3.14}/src/agentpack/analysis/role_inference.py +0 -0
- {agentpack_cli-0.3.13 → agentpack_cli-0.3.14}/src/agentpack/analysis/rust_imports.py +0 -0
- {agentpack_cli-0.3.13 → agentpack_cli-0.3.14}/src/agentpack/analysis/symbols.py +0 -0
- {agentpack_cli-0.3.13 → agentpack_cli-0.3.14}/src/agentpack/analysis/task_classifier.py +0 -0
- {agentpack_cli-0.3.13 → agentpack_cli-0.3.14}/src/agentpack/analysis/tests.py +0 -0
- {agentpack_cli-0.3.13 → agentpack_cli-0.3.14}/src/agentpack/application/__init__.py +0 -0
- {agentpack_cli-0.3.13 → agentpack_cli-0.3.14}/src/agentpack/cli.py +0 -0
- {agentpack_cli-0.3.13 → agentpack_cli-0.3.14}/src/agentpack/commands/__init__.py +0 -0
- {agentpack_cli-0.3.13 → agentpack_cli-0.3.14}/src/agentpack/commands/_shared.py +0 -0
- {agentpack_cli-0.3.13 → agentpack_cli-0.3.14}/src/agentpack/commands/ci_cmd.py +0 -0
- {agentpack_cli-0.3.13 → agentpack_cli-0.3.14}/src/agentpack/commands/claude_cmd.py +0 -0
- {agentpack_cli-0.3.13 → agentpack_cli-0.3.14}/src/agentpack/commands/diagnose_selection.py +0 -0
- {agentpack_cli-0.3.13 → agentpack_cli-0.3.14}/src/agentpack/commands/diff.py +0 -0
- {agentpack_cli-0.3.13 → agentpack_cli-0.3.14}/src/agentpack/commands/doctor.py +0 -0
- {agentpack_cli-0.3.13 → agentpack_cli-0.3.14}/src/agentpack/commands/eval_cmd.py +0 -0
- {agentpack_cli-0.3.13 → agentpack_cli-0.3.14}/src/agentpack/commands/explain.py +0 -0
- {agentpack_cli-0.3.13 → agentpack_cli-0.3.14}/src/agentpack/commands/guard.py +0 -0
- {agentpack_cli-0.3.13 → agentpack_cli-0.3.14}/src/agentpack/commands/hook_cmd.py +0 -0
- {agentpack_cli-0.3.13 → agentpack_cli-0.3.14}/src/agentpack/commands/ignore_cmd.py +0 -0
- {agentpack_cli-0.3.13 → agentpack_cli-0.3.14}/src/agentpack/commands/install.py +0 -0
- {agentpack_cli-0.3.13 → agentpack_cli-0.3.14}/src/agentpack/commands/mcp_cmd.py +0 -0
- {agentpack_cli-0.3.13 → agentpack_cli-0.3.14}/src/agentpack/commands/migrate.py +0 -0
- {agentpack_cli-0.3.13 → agentpack_cli-0.3.14}/src/agentpack/commands/monitor.py +0 -0
- {agentpack_cli-0.3.13 → agentpack_cli-0.3.14}/src/agentpack/commands/next_cmd.py +0 -0
- {agentpack_cli-0.3.13 → agentpack_cli-0.3.14}/src/agentpack/commands/pack.py +0 -0
- {agentpack_cli-0.3.13 → agentpack_cli-0.3.14}/src/agentpack/commands/quickstart.py +0 -0
- {agentpack_cli-0.3.13 → agentpack_cli-0.3.14}/src/agentpack/commands/release_cmd.py +0 -0
- {agentpack_cli-0.3.13 → agentpack_cli-0.3.14}/src/agentpack/commands/repair.py +0 -0
- {agentpack_cli-0.3.13 → agentpack_cli-0.3.14}/src/agentpack/commands/route.py +0 -0
- {agentpack_cli-0.3.13 → agentpack_cli-0.3.14}/src/agentpack/commands/scan.py +0 -0
- {agentpack_cli-0.3.13 → agentpack_cli-0.3.14}/src/agentpack/commands/skills.py +0 -0
- {agentpack_cli-0.3.13 → agentpack_cli-0.3.14}/src/agentpack/commands/start_cmd.py +0 -0
- {agentpack_cli-0.3.13 → agentpack_cli-0.3.14}/src/agentpack/commands/state_cmd.py +0 -0
- {agentpack_cli-0.3.13 → agentpack_cli-0.3.14}/src/agentpack/commands/stats.py +0 -0
- {agentpack_cli-0.3.13 → agentpack_cli-0.3.14}/src/agentpack/commands/status.py +0 -0
- {agentpack_cli-0.3.13 → agentpack_cli-0.3.14}/src/agentpack/commands/summarize.py +0 -0
- {agentpack_cli-0.3.13 → agentpack_cli-0.3.14}/src/agentpack/commands/task_cmd.py +0 -0
- {agentpack_cli-0.3.13 → agentpack_cli-0.3.14}/src/agentpack/commands/threads.py +0 -0
- {agentpack_cli-0.3.13 → agentpack_cli-0.3.14}/src/agentpack/commands/tune.py +0 -0
- {agentpack_cli-0.3.13 → agentpack_cli-0.3.14}/src/agentpack/commands/verify_wheel.py +0 -0
- {agentpack_cli-0.3.13 → agentpack_cli-0.3.14}/src/agentpack/commands/watch.py +0 -0
- {agentpack_cli-0.3.13 → agentpack_cli-0.3.14}/src/agentpack/commands/workflow_cmd.py +0 -0
- {agentpack_cli-0.3.13 → agentpack_cli-0.3.14}/src/agentpack/core/__init__.py +0 -0
- {agentpack_cli-0.3.13 → agentpack_cli-0.3.14}/src/agentpack/core/bootstrap.py +0 -0
- {agentpack_cli-0.3.13 → agentpack_cli-0.3.14}/src/agentpack/core/cache.py +0 -0
- {agentpack_cli-0.3.13 → agentpack_cli-0.3.14}/src/agentpack/core/changed_paths.py +0 -0
- {agentpack_cli-0.3.13 → agentpack_cli-0.3.14}/src/agentpack/core/context_pack.py +0 -0
- {agentpack_cli-0.3.13 → agentpack_cli-0.3.14}/src/agentpack/core/diff.py +0 -0
- {agentpack_cli-0.3.13 → agentpack_cli-0.3.14}/src/agentpack/core/evals.py +0 -0
- {agentpack_cli-0.3.13 → agentpack_cli-0.3.14}/src/agentpack/core/execution_state.py +0 -0
- {agentpack_cli-0.3.13 → agentpack_cli-0.3.14}/src/agentpack/core/git.py +0 -0
- {agentpack_cli-0.3.13 → agentpack_cli-0.3.14}/src/agentpack/core/git_hooks.py +0 -0
- {agentpack_cli-0.3.13 → agentpack_cli-0.3.14}/src/agentpack/core/global_install.py +0 -0
- {agentpack_cli-0.3.13 → agentpack_cli-0.3.14}/src/agentpack/core/ignore.py +0 -0
- {agentpack_cli-0.3.13 → agentpack_cli-0.3.14}/src/agentpack/core/merkle.py +0 -0
- {agentpack_cli-0.3.13 → agentpack_cli-0.3.14}/src/agentpack/core/models.py +0 -0
- {agentpack_cli-0.3.13 → agentpack_cli-0.3.14}/src/agentpack/core/redactor.py +0 -0
- {agentpack_cli-0.3.13 → agentpack_cli-0.3.14}/src/agentpack/core/scanner.py +0 -0
- {agentpack_cli-0.3.13 → agentpack_cli-0.3.14}/src/agentpack/core/snapshot.py +0 -0
- {agentpack_cli-0.3.13 → agentpack_cli-0.3.14}/src/agentpack/core/task_freshness.py +0 -0
- {agentpack_cli-0.3.13 → agentpack_cli-0.3.14}/src/agentpack/core/thread_context.py +0 -0
- {agentpack_cli-0.3.13 → agentpack_cli-0.3.14}/src/agentpack/core/token_estimator.py +0 -0
- {agentpack_cli-0.3.13 → agentpack_cli-0.3.14}/src/agentpack/core/vscode_tasks.py +0 -0
- {agentpack_cli-0.3.13 → agentpack_cli-0.3.14}/src/agentpack/data/agentpack.md +0 -0
- {agentpack_cli-0.3.13 → agentpack_cli-0.3.14}/src/agentpack/installers/__init__.py +0 -0
- {agentpack_cli-0.3.13 → agentpack_cli-0.3.14}/src/agentpack/installers/antigravity.py +0 -0
- {agentpack_cli-0.3.13 → agentpack_cli-0.3.14}/src/agentpack/installers/claude.py +0 -0
- {agentpack_cli-0.3.13 → agentpack_cli-0.3.14}/src/agentpack/installers/codex.py +0 -0
- {agentpack_cli-0.3.13 → agentpack_cli-0.3.14}/src/agentpack/installers/cursor.py +0 -0
- {agentpack_cli-0.3.13 → agentpack_cli-0.3.14}/src/agentpack/installers/windsurf.py +0 -0
- {agentpack_cli-0.3.13 → agentpack_cli-0.3.14}/src/agentpack/integrations/__init__.py +0 -0
- {agentpack_cli-0.3.13 → agentpack_cli-0.3.14}/src/agentpack/integrations/agents.py +0 -0
- {agentpack_cli-0.3.13 → agentpack_cli-0.3.14}/src/agentpack/integrations/git_hooks.py +0 -0
- {agentpack_cli-0.3.13 → agentpack_cli-0.3.14}/src/agentpack/integrations/global_install.py +0 -0
- {agentpack_cli-0.3.13 → agentpack_cli-0.3.14}/src/agentpack/integrations/platform.py +0 -0
- {agentpack_cli-0.3.13 → agentpack_cli-0.3.14}/src/agentpack/integrations/vscode_tasks.py +0 -0
- {agentpack_cli-0.3.13 → agentpack_cli-0.3.14}/src/agentpack/learning/collector.py +0 -0
- {agentpack_cli-0.3.13 → agentpack_cli-0.3.14}/src/agentpack/learning/extractor.py +0 -0
- {agentpack_cli-0.3.13 → agentpack_cli-0.3.14}/src/agentpack/learning/quality.py +0 -0
- {agentpack_cli-0.3.13 → agentpack_cli-0.3.14}/src/agentpack/mcp_server.py +0 -0
- {agentpack_cli-0.3.13 → agentpack_cli-0.3.14}/src/agentpack/renderers/__init__.py +0 -0
- {agentpack_cli-0.3.13 → agentpack_cli-0.3.14}/src/agentpack/renderers/compact.py +0 -0
- {agentpack_cli-0.3.13 → agentpack_cli-0.3.14}/src/agentpack/renderers/markdown.py +0 -0
- {agentpack_cli-0.3.13 → agentpack_cli-0.3.14}/src/agentpack/renderers/receipts.py +0 -0
- {agentpack_cli-0.3.13 → agentpack_cli-0.3.14}/src/agentpack/router/__init__.py +0 -0
- {agentpack_cli-0.3.13 → agentpack_cli-0.3.14}/src/agentpack/router/discovery.py +0 -0
- {agentpack_cli-0.3.13 → agentpack_cli-0.3.14}/src/agentpack/router/models.py +0 -0
- {agentpack_cli-0.3.13 → agentpack_cli-0.3.14}/src/agentpack/router/parser.py +0 -0
- {agentpack_cli-0.3.13 → agentpack_cli-0.3.14}/src/agentpack/router/prompt_builder.py +0 -0
- {agentpack_cli-0.3.13 → agentpack_cli-0.3.14}/src/agentpack/router/scoring.py +0 -0
- {agentpack_cli-0.3.13 → agentpack_cli-0.3.14}/src/agentpack/router/service.py +0 -0
- {agentpack_cli-0.3.13 → agentpack_cli-0.3.14}/src/agentpack/session/__init__.py +0 -0
- {agentpack_cli-0.3.13 → agentpack_cli-0.3.14}/src/agentpack/session/state.py +0 -0
- {agentpack_cli-0.3.13 → agentpack_cli-0.3.14}/src/agentpack/summaries/__init__.py +0 -0
- {agentpack_cli-0.3.13 → agentpack_cli-0.3.14}/src/agentpack/summaries/base.py +0 -0
- {agentpack_cli-0.3.13 → agentpack_cli-0.3.14}/src/agentpack/summaries/offline.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: agentpack-cli
|
|
3
|
-
Version: 0.3.
|
|
3
|
+
Version: 0.3.14
|
|
4
4
|
Summary: Local MCP context router for Claude Code, Codex, Cursor, and AI coding agents.
|
|
5
5
|
License: MIT
|
|
6
6
|
License-File: LICENSE
|
|
@@ -63,7 +63,7 @@ pipx run --spec agentpack-cli agentpack route --task "fix auth token expiry"
|
|
|
63
63
|
|
|
64
64
|

|
|
65
65
|
|
|
66
|
-
> **Status: alpha (v0.3.
|
|
66
|
+
> **Status: alpha (v0.3.14).** Works, tested, and used in real sessions. Python and JavaScript/TypeScript are the best-supported languages. Current benchmarks are useful regression checks, not broad proof that AgentPack improves coding-agent success. API may change before 1.0.
|
|
67
67
|
>
|
|
68
68
|
> **Platform note:** macOS, Linux, and Windows are supported. Windows support targets PowerShell plus Git for Windows. `cmd.exe` and bare Git setups are not a supported path yet.
|
|
69
69
|
>
|
|
@@ -205,10 +205,16 @@ agentpack learn --today
|
|
|
205
205
|
agentpack learn --since main
|
|
206
206
|
agentpack learn --json
|
|
207
207
|
agentpack learn --llm-prompt --pr-comment
|
|
208
|
-
agentpack learn --
|
|
208
|
+
agentpack learn --provider-preview
|
|
209
|
+
agentpack learn --provider-command "python scripts/learn_provider.py"
|
|
210
|
+
agentpack learn --dashboard --team-export
|
|
211
|
+
agentpack learn --skills
|
|
212
|
+
agentpack learn --drills
|
|
213
|
+
agentpack learn --ci
|
|
214
|
+
agentpack learn --feedback helpful --feedback-target "skill:CLI design" --feedback-note "Useful review prompts"
|
|
209
215
|
```
|
|
210
216
|
|
|
211
|
-
AgentPack writes developer notes to `.agentpack/learning.md` or `.agentpack/daily-summary.md`, updates `.agentpack/skills-progress.json`, writes `.agentpack/agent-lessons.md` for future coding agents, and can emit `.agentpack/learning.prompt.md
|
|
217
|
+
AgentPack writes developer notes to `.agentpack/learning.md` or `.agentpack/daily-summary.md`, updates a local skill memory in `.agentpack/skills-progress.json`, writes ranked `.agentpack/agent-lessons.md` for future coding agents, and can emit `.agentpack/learning.prompt.md`, `.agentpack/pr-learning-comment.md`, `.agentpack/learning-dashboard.html`, or `.agentpack/team-lessons.md`. Learn is local-first by default: `--provider-preview` shows the bounded payload for optional external refinement without making a network call, `--provider-command` runs only the local command you provide, and feedback stays in `.agentpack/learning-feedback.jsonl`.
|
|
212
218
|
|
|
213
219
|
## Agent Setup
|
|
214
220
|
|
|
@@ -319,7 +325,7 @@ gate.
|
|
|
319
325
|
| `agentpack work "task"` | Initialize if needed, start task, refresh context, show next steps |
|
|
320
326
|
| `agentpack start "task"` | Write task and run the guard/refresh workflow |
|
|
321
327
|
| `agentpack finish --since main` | Diagnose, capture benchmark case, run checks, mark done |
|
|
322
|
-
| `agentpack learn` | Generate developer learning notes, skill
|
|
328
|
+
| `agentpack learn` | Generate developer learning notes, skill memory, feedback-aware drills, and future-agent lessons |
|
|
323
329
|
| `agentpack task show|set|clear` | Manage global or thread-scoped task files |
|
|
324
330
|
| `agentpack pack` | Generate a ranked context pack for `.agentpack/task.md` |
|
|
325
331
|
| `agentpack next --fix-all-safe` | Ask AgentPack what command or safe repair should happen next |
|
|
@@ -24,7 +24,7 @@ pipx run --spec agentpack-cli agentpack route --task "fix auth token expiry"
|
|
|
24
24
|
|
|
25
25
|

|
|
26
26
|
|
|
27
|
-
> **Status: alpha (v0.3.
|
|
27
|
+
> **Status: alpha (v0.3.14).** Works, tested, and used in real sessions. Python and JavaScript/TypeScript are the best-supported languages. Current benchmarks are useful regression checks, not broad proof that AgentPack improves coding-agent success. API may change before 1.0.
|
|
28
28
|
>
|
|
29
29
|
> **Platform note:** macOS, Linux, and Windows are supported. Windows support targets PowerShell plus Git for Windows. `cmd.exe` and bare Git setups are not a supported path yet.
|
|
30
30
|
>
|
|
@@ -166,10 +166,16 @@ agentpack learn --today
|
|
|
166
166
|
agentpack learn --since main
|
|
167
167
|
agentpack learn --json
|
|
168
168
|
agentpack learn --llm-prompt --pr-comment
|
|
169
|
-
agentpack learn --
|
|
169
|
+
agentpack learn --provider-preview
|
|
170
|
+
agentpack learn --provider-command "python scripts/learn_provider.py"
|
|
171
|
+
agentpack learn --dashboard --team-export
|
|
172
|
+
agentpack learn --skills
|
|
173
|
+
agentpack learn --drills
|
|
174
|
+
agentpack learn --ci
|
|
175
|
+
agentpack learn --feedback helpful --feedback-target "skill:CLI design" --feedback-note "Useful review prompts"
|
|
170
176
|
```
|
|
171
177
|
|
|
172
|
-
AgentPack writes developer notes to `.agentpack/learning.md` or `.agentpack/daily-summary.md`, updates `.agentpack/skills-progress.json`, writes `.agentpack/agent-lessons.md` for future coding agents, and can emit `.agentpack/learning.prompt.md
|
|
178
|
+
AgentPack writes developer notes to `.agentpack/learning.md` or `.agentpack/daily-summary.md`, updates a local skill memory in `.agentpack/skills-progress.json`, writes ranked `.agentpack/agent-lessons.md` for future coding agents, and can emit `.agentpack/learning.prompt.md`, `.agentpack/pr-learning-comment.md`, `.agentpack/learning-dashboard.html`, or `.agentpack/team-lessons.md`. Learn is local-first by default: `--provider-preview` shows the bounded payload for optional external refinement without making a network call, `--provider-command` runs only the local command you provide, and feedback stays in `.agentpack/learning-feedback.jsonl`.
|
|
173
179
|
|
|
174
180
|
## Agent Setup
|
|
175
181
|
|
|
@@ -280,7 +286,7 @@ gate.
|
|
|
280
286
|
| `agentpack work "task"` | Initialize if needed, start task, refresh context, show next steps |
|
|
281
287
|
| `agentpack start "task"` | Write task and run the guard/refresh workflow |
|
|
282
288
|
| `agentpack finish --since main` | Diagnose, capture benchmark case, run checks, mark done |
|
|
283
|
-
| `agentpack learn` | Generate developer learning notes, skill
|
|
289
|
+
| `agentpack learn` | Generate developer learning notes, skill memory, feedback-aware drills, and future-agent lessons |
|
|
284
290
|
| `agentpack task show|set|clear` | Manage global or thread-scoped task files |
|
|
285
291
|
| `agentpack pack` | Generate a ranked context pack for `.agentpack/task.md` |
|
|
286
292
|
| `agentpack next --fix-all-safe` | Ask AgentPack what command or safe repair should happen next |
|
|
@@ -660,6 +660,8 @@ class AdapterRegistry:
|
|
|
660
660
|
cfg.learning.agent_lessons_output,
|
|
661
661
|
cfg.learning.llm_prompt_output,
|
|
662
662
|
cfg.learning.pr_comment_output,
|
|
663
|
+
cfg.learning.dashboard_output,
|
|
664
|
+
cfg.learning.team_lessons_output,
|
|
663
665
|
cfg.learning.feedback_output,
|
|
664
666
|
}
|
|
665
667
|
)
|
|
@@ -469,6 +469,26 @@ def _run_git(cwd: Path | None, args: list[str]) -> None:
|
|
|
469
469
|
)
|
|
470
470
|
|
|
471
471
|
|
|
472
|
+
def _git_commit_exists(cwd: Path, commit: str) -> bool:
|
|
473
|
+
result = subprocess.run(
|
|
474
|
+
["git", "cat-file", "-e", f"{commit}^{{commit}}"],
|
|
475
|
+
cwd=cwd,
|
|
476
|
+
text=True,
|
|
477
|
+
stdout=subprocess.PIPE,
|
|
478
|
+
stderr=subprocess.PIPE,
|
|
479
|
+
check=False,
|
|
480
|
+
)
|
|
481
|
+
return result.returncode == 0
|
|
482
|
+
|
|
483
|
+
|
|
484
|
+
def _ensure_git_commit(cwd: Path, commit: str) -> None:
|
|
485
|
+
if _git_commit_exists(cwd, commit):
|
|
486
|
+
return
|
|
487
|
+
_run_git(cwd, ["fetch", "--quiet", "--depth", "1", "origin", commit])
|
|
488
|
+
if not _git_commit_exists(cwd, commit):
|
|
489
|
+
raise RuntimeError(f"Unable to fetch public benchmark commit {commit}")
|
|
490
|
+
|
|
491
|
+
|
|
472
492
|
def _ensure_public_repo_clone(
|
|
473
493
|
spec: PublicRepoSpec,
|
|
474
494
|
cache_dir: Path,
|
|
@@ -512,7 +532,9 @@ def _run_public_repo_suite(
|
|
|
512
532
|
for spec in specs:
|
|
513
533
|
source_repo = _ensure_public_repo_clone(spec, cache, refresh=refresh)
|
|
514
534
|
for public_case in spec.cases:
|
|
535
|
+
_ensure_git_commit(source_repo, public_case.commit)
|
|
515
536
|
parent = _git_stdout(source_repo, ["rev-parse", f"{public_case.commit}^"])
|
|
537
|
+
_ensure_git_commit(source_repo, parent)
|
|
516
538
|
work_root = temp_root / f"{spec.name}-{public_case.commit[:8]}"
|
|
517
539
|
shutil.copytree(
|
|
518
540
|
source_repo,
|
|
@@ -40,6 +40,8 @@ def register(app: typer.Typer) -> None:
|
|
|
40
40
|
marker = "[green]✓[/]" if item["returncode"] == 0 else "[red]✗[/]"
|
|
41
41
|
console.print(f"{marker} {item['name']} ({item['duration_s']:.2f}s)")
|
|
42
42
|
if item["returncode"] != 0:
|
|
43
|
+
if item["output_excerpt"]:
|
|
44
|
+
console.print(item["output_excerpt"])
|
|
43
45
|
console.print(f" rerun: [bold]{item['command']}[/]")
|
|
44
46
|
if failed:
|
|
45
47
|
raise typer.Exit(1)
|
|
@@ -48,10 +50,21 @@ def register(app: typer.Typer) -> None:
|
|
|
48
50
|
def _run(stage: CheckStage) -> dict[str, Any]:
|
|
49
51
|
started = time.perf_counter()
|
|
50
52
|
result = subprocess.run(stage.command, cwd=_root(), capture_output=True, text=True)
|
|
53
|
+
output = (result.stdout + "\n" + result.stderr).strip()
|
|
51
54
|
return {
|
|
52
55
|
"name": stage.name,
|
|
53
56
|
"command": " ".join(stage.command),
|
|
54
57
|
"returncode": result.returncode,
|
|
55
58
|
"duration_s": round(time.perf_counter() - started, 3),
|
|
56
|
-
"detail": (
|
|
59
|
+
"detail": (output.splitlines() or [""])[-1],
|
|
60
|
+
"output_excerpt": _output_excerpt(output) if result.returncode != 0 else "",
|
|
57
61
|
}
|
|
62
|
+
|
|
63
|
+
|
|
64
|
+
def _output_excerpt(output: str, *, max_lines: int = 80) -> str:
|
|
65
|
+
lines = output.splitlines()
|
|
66
|
+
if len(lines) <= max_lines:
|
|
67
|
+
excerpt = lines
|
|
68
|
+
else:
|
|
69
|
+
excerpt = ["... output truncated to final failing lines ...", *lines[-max_lines:]]
|
|
70
|
+
return "\n".join(f" {line}" for line in excerpt)
|
|
@@ -68,6 +68,8 @@ def _repo_gitignore_entries(share_cache: bool = False, agent: str = "generic") -
|
|
|
68
68
|
".agentpack/agent-lessons.md",
|
|
69
69
|
".agentpack/learning.prompt.md",
|
|
70
70
|
".agentpack/pr-learning-comment.md",
|
|
71
|
+
".agentpack/learning-dashboard.html",
|
|
72
|
+
".agentpack/team-lessons.md",
|
|
71
73
|
".agentpack/learning-feedback.jsonl",
|
|
72
74
|
".agentignore",
|
|
73
75
|
]
|
|
@@ -107,6 +109,8 @@ def _agentpack_gitignore_content(share_cache: bool = False) -> str:
|
|
|
107
109
|
"agent-lessons.md",
|
|
108
110
|
"learning.prompt.md",
|
|
109
111
|
"pr-learning-comment.md",
|
|
112
|
+
"learning-dashboard.html",
|
|
113
|
+
"team-lessons.md",
|
|
110
114
|
"learning-feedback.jsonl",
|
|
111
115
|
]
|
|
112
116
|
)
|
|
@@ -0,0 +1,179 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
import json
|
|
4
|
+
from datetime import datetime
|
|
5
|
+
|
|
6
|
+
import typer
|
|
7
|
+
|
|
8
|
+
from agentpack.commands._shared import _atomic_write, _root, console
|
|
9
|
+
from agentpack.core.config import load_config
|
|
10
|
+
from agentpack.learning.collector import collect_learning_inputs
|
|
11
|
+
from agentpack.learning.extractor import build_learning_report
|
|
12
|
+
from agentpack.learning.feedback import apply_feedback_to_report, load_feedback_summary, record_learning_feedback
|
|
13
|
+
from agentpack.learning.lesson_ranker import rank_agent_lessons
|
|
14
|
+
from agentpack.learning.provider import LearningProviderError, run_provider_command
|
|
15
|
+
from agentpack.learning.quality import score_learning_report
|
|
16
|
+
from agentpack.learning.renderers import (
|
|
17
|
+
learning_report_to_dict,
|
|
18
|
+
render_dashboard_html,
|
|
19
|
+
render_agent_lessons_markdown,
|
|
20
|
+
render_drills_markdown,
|
|
21
|
+
render_llm_prompt_markdown,
|
|
22
|
+
render_learning_markdown,
|
|
23
|
+
render_pr_comment_markdown,
|
|
24
|
+
render_provider_preview_markdown,
|
|
25
|
+
render_quality_markdown,
|
|
26
|
+
render_team_lessons_markdown,
|
|
27
|
+
)
|
|
28
|
+
from agentpack.learning.skill_map import apply_skill_feedback, recommend_practice_drills, render_skill_summary, update_skill_map
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
def register(app: typer.Typer) -> None:
|
|
32
|
+
@app.command()
|
|
33
|
+
def learn(
|
|
34
|
+
task: str = typer.Option("auto", "--task", help="Task source. Only 'auto' is supported."),
|
|
35
|
+
since: str | None = typer.Option(None, "--since", help="Git ref to compare against, e.g. HEAD~1 or main."),
|
|
36
|
+
today: bool = typer.Option(False, "--today", help="Use today's work scope label for the report."),
|
|
37
|
+
output: str = typer.Option("", "--output", "-o", help="Markdown output path."),
|
|
38
|
+
json_output: bool = typer.Option(False, "--json", help="Print JSON to stdout instead of writing Markdown."),
|
|
39
|
+
llm_prompt: bool = typer.Option(False, "--llm-prompt", help="Write an LLM-ready learning prompt artifact."),
|
|
40
|
+
pr_comment: bool = typer.Option(False, "--pr-comment", help="Write a PR-comment-ready learning summary artifact."),
|
|
41
|
+
provider_preview: bool = typer.Option(False, "--provider-preview", help="Print the bounded provider payload without making a network call."),
|
|
42
|
+
provider_command: str = typer.Option("", "--provider-command", help="Run a local JSON-in/JSON-out provider command to enrich the report."),
|
|
43
|
+
dashboard: bool = typer.Option(False, "--dashboard", help="Write a static HTML learning dashboard artifact."),
|
|
44
|
+
team_export: bool = typer.Option(False, "--team-export", help="Write an opt-in team lesson export without personal skill history."),
|
|
45
|
+
ci: bool = typer.Option(False, "--ci", help="Fail when learning quality is below the configured threshold."),
|
|
46
|
+
skills: bool = typer.Option(False, "--skills", help="Print the local skill memory summary and exit."),
|
|
47
|
+
drills: bool = typer.Option(False, "--drills", help="Print recommended practice drills from local skill memory and exit."),
|
|
48
|
+
feedback: str = typer.Option("", "--feedback", help="Record feedback for this learning output (helpful|not-helpful)."),
|
|
49
|
+
feedback_note: str = typer.Option("", "--feedback-note", help="Optional note stored with --feedback."),
|
|
50
|
+
feedback_target: str = typer.Option("", "--feedback-target", help="Optional target such as skill:CLI design, lesson:retry, rename:old=>new, or merge:old=>new."),
|
|
51
|
+
suppress_skill: str = typer.Option("", "--suppress-skill", help="Suppress a noisy skill in future skill views and generation."),
|
|
52
|
+
rename_skill: str = typer.Option("", "--rename-skill", help="Rename a skill using old=>new."),
|
|
53
|
+
merge_skill: str = typer.Option("", "--merge-skill", help="Merge a skill using old=>new."),
|
|
54
|
+
) -> None:
|
|
55
|
+
"""Generate local learning artifacts from current task and git changes."""
|
|
56
|
+
if task != "auto":
|
|
57
|
+
console.print(
|
|
58
|
+
"[red]`agentpack learn --task \"...\"` is not supported. "
|
|
59
|
+
"Write .agentpack/task.md and use --task auto.[/]"
|
|
60
|
+
)
|
|
61
|
+
raise typer.Exit(2)
|
|
62
|
+
|
|
63
|
+
root = _root()
|
|
64
|
+
cfg = load_config(root)
|
|
65
|
+
skill_map_path = root / cfg.learning.skill_map_output
|
|
66
|
+
if skills:
|
|
67
|
+
typer.echo(render_skill_summary(skill_map_path), nl=False)
|
|
68
|
+
return
|
|
69
|
+
if drills:
|
|
70
|
+
typer.echo(render_drills_markdown(recommend_practice_drills(skill_map_path)), nl=False)
|
|
71
|
+
return
|
|
72
|
+
if suppress_skill:
|
|
73
|
+
apply_skill_feedback(skill_map_path, target=suppress_skill, action="suppress", note=feedback_note)
|
|
74
|
+
console.print(f"[green]✓[/] Suppressed skill {suppress_skill}")
|
|
75
|
+
return
|
|
76
|
+
if rename_skill:
|
|
77
|
+
old, new = _split_mapping(rename_skill, "--rename-skill")
|
|
78
|
+
apply_skill_feedback(skill_map_path, target=old, action="rename", replacement=new, note=feedback_note)
|
|
79
|
+
console.print(f"[green]✓[/] Renamed skill {old} -> {new}")
|
|
80
|
+
return
|
|
81
|
+
if merge_skill:
|
|
82
|
+
old, new = _split_mapping(merge_skill, "--merge-skill")
|
|
83
|
+
apply_skill_feedback(skill_map_path, target=old, action="merge", replacement=new, note=feedback_note)
|
|
84
|
+
console.print(f"[green]✓[/] Merged skill {old} -> {new}")
|
|
85
|
+
return
|
|
86
|
+
|
|
87
|
+
since_date = _today_start_iso() if today and not since else None
|
|
88
|
+
inputs = collect_learning_inputs(
|
|
89
|
+
root,
|
|
90
|
+
since=since,
|
|
91
|
+
since_date=since_date,
|
|
92
|
+
max_changed_files=cfg.learning.max_changed_files,
|
|
93
|
+
max_diff_chars_per_file=cfg.learning.max_diff_chars_per_file,
|
|
94
|
+
)
|
|
95
|
+
report = build_learning_report(
|
|
96
|
+
inputs,
|
|
97
|
+
max_cards=cfg.learning.max_cards,
|
|
98
|
+
max_quiz_questions=cfg.learning.max_quiz_questions,
|
|
99
|
+
)
|
|
100
|
+
feedback_summary = load_feedback_summary(root / cfg.learning.feedback_output)
|
|
101
|
+
report = apply_feedback_to_report(report, feedback_summary)
|
|
102
|
+
report.agent_lessons = rank_agent_lessons(report, feedback_summary, limit=cfg.learning.max_cards)
|
|
103
|
+
if today:
|
|
104
|
+
report.scope = "today"
|
|
105
|
+
if since_date:
|
|
106
|
+
report.since = f"today ({since_date})"
|
|
107
|
+
|
|
108
|
+
if provider_preview:
|
|
109
|
+
typer.echo(render_provider_preview_markdown(report), nl=False)
|
|
110
|
+
return
|
|
111
|
+
|
|
112
|
+
command = provider_command or cfg.learning.provider_command
|
|
113
|
+
if command:
|
|
114
|
+
try:
|
|
115
|
+
report = run_provider_command(command, report, timeout_seconds=cfg.learning.provider_timeout_seconds)
|
|
116
|
+
except LearningProviderError as exc:
|
|
117
|
+
console.print(f"[red]Provider command failed:[/] {exc}")
|
|
118
|
+
raise typer.Exit(1) from exc
|
|
119
|
+
|
|
120
|
+
update_skill_map(skill_map_path, report.skill_evidence)
|
|
121
|
+
agent_lessons_path = root / cfg.learning.agent_lessons_output
|
|
122
|
+
agent_lessons_path.parent.mkdir(parents=True, exist_ok=True)
|
|
123
|
+
_atomic_write(agent_lessons_path, render_agent_lessons_markdown(report))
|
|
124
|
+
|
|
125
|
+
quality = score_learning_report(report)
|
|
126
|
+
if quality.score < cfg.learning.min_groundedness_score:
|
|
127
|
+
console.print(f"[yellow]Learning quality warning:[/] score {quality.score}; " + "; ".join(quality.issues))
|
|
128
|
+
|
|
129
|
+
if llm_prompt:
|
|
130
|
+
prompt_path = root / cfg.learning.llm_prompt_output
|
|
131
|
+
prompt_path.parent.mkdir(parents=True, exist_ok=True)
|
|
132
|
+
_atomic_write(prompt_path, render_llm_prompt_markdown(report))
|
|
133
|
+
if pr_comment:
|
|
134
|
+
pr_path = root / cfg.learning.pr_comment_output
|
|
135
|
+
pr_path.parent.mkdir(parents=True, exist_ok=True)
|
|
136
|
+
_atomic_write(pr_path, render_pr_comment_markdown(report))
|
|
137
|
+
if dashboard:
|
|
138
|
+
dashboard_path = root / cfg.learning.dashboard_output
|
|
139
|
+
dashboard_path.parent.mkdir(parents=True, exist_ok=True)
|
|
140
|
+
_atomic_write(dashboard_path, render_dashboard_html(report))
|
|
141
|
+
if team_export:
|
|
142
|
+
team_path = root / cfg.learning.team_lessons_output
|
|
143
|
+
team_path.parent.mkdir(parents=True, exist_ok=True)
|
|
144
|
+
_atomic_write(team_path, render_team_lessons_markdown(report))
|
|
145
|
+
if feedback:
|
|
146
|
+
if feedback not in {"helpful", "not-helpful"}:
|
|
147
|
+
console.print("[red]--feedback must be helpful or not-helpful.[/]")
|
|
148
|
+
raise typer.Exit(2)
|
|
149
|
+
record_learning_feedback(root / cfg.learning.feedback_output, report, feedback, feedback_note, feedback_target)
|
|
150
|
+
if ci:
|
|
151
|
+
typer.echo(render_quality_markdown(report, quality.score, quality.issues), nl=False)
|
|
152
|
+
if quality.score < cfg.learning.min_groundedness_score:
|
|
153
|
+
raise typer.Exit(1)
|
|
154
|
+
|
|
155
|
+
if json_output:
|
|
156
|
+
typer.echo(json.dumps(learning_report_to_dict(report), indent=2, sort_keys=True))
|
|
157
|
+
return
|
|
158
|
+
|
|
159
|
+
default_output = cfg.learning.daily_output if today else cfg.learning.markdown_output
|
|
160
|
+
out_path = root / (output or default_output)
|
|
161
|
+
out_path.parent.mkdir(parents=True, exist_ok=True)
|
|
162
|
+
_atomic_write(out_path, render_learning_markdown(report))
|
|
163
|
+
console.print(f"[green]✓[/] Wrote {out_path.relative_to(root)}")
|
|
164
|
+
|
|
165
|
+
|
|
166
|
+
def _today_start_iso() -> str:
|
|
167
|
+
now = datetime.now().astimezone()
|
|
168
|
+
return now.replace(hour=0, minute=0, second=0, microsecond=0).isoformat()
|
|
169
|
+
|
|
170
|
+
|
|
171
|
+
def _split_mapping(value: str, flag: str) -> tuple[str, str]:
|
|
172
|
+
if "=>" not in value:
|
|
173
|
+
console.print(f"[red]{flag} expects old=>new.[/]")
|
|
174
|
+
raise typer.Exit(2)
|
|
175
|
+
old, new = [part.strip() for part in value.split("=>", 1)]
|
|
176
|
+
if not old or not new:
|
|
177
|
+
console.print(f"[red]{flag} expects non-empty old=>new values.[/]")
|
|
178
|
+
raise typer.Exit(2)
|
|
179
|
+
return old, new
|
|
@@ -23,6 +23,7 @@ class StageResult:
|
|
|
23
23
|
duration_s: float
|
|
24
24
|
returncode: int = 0
|
|
25
25
|
detail: str = ""
|
|
26
|
+
output_excerpt: str = ""
|
|
26
27
|
|
|
27
28
|
def as_dict(self) -> dict[str, Any]:
|
|
28
29
|
return {
|
|
@@ -32,6 +33,7 @@ class StageResult:
|
|
|
32
33
|
"duration_s": round(self.duration_s, 3),
|
|
33
34
|
"returncode": self.returncode,
|
|
34
35
|
"detail": self.detail,
|
|
36
|
+
"output_excerpt": self.output_excerpt,
|
|
35
37
|
}
|
|
36
38
|
|
|
37
39
|
|
|
@@ -64,6 +66,8 @@ def register(app: typer.Typer) -> None:
|
|
|
64
66
|
console.print(f"{marker} {stage.name}: {stage.status} ({stage.duration_s:.2f}s)")
|
|
65
67
|
if stage.detail and stage.status != "passed":
|
|
66
68
|
console.print(f" {stage.detail}")
|
|
69
|
+
if stage.output_excerpt and stage.status != "passed":
|
|
70
|
+
console.print(stage.output_excerpt)
|
|
67
71
|
if stage.status != "passed":
|
|
68
72
|
console.print(f" rerun: [bold]{stage.command}[/]")
|
|
69
73
|
if failed:
|
|
@@ -93,7 +97,8 @@ def _run_stage(root: Path, name: str, command: list[str]) -> StageResult:
|
|
|
93
97
|
result = subprocess.run(command, cwd=root, capture_output=True, text=True)
|
|
94
98
|
except OSError as exc:
|
|
95
99
|
return StageResult(name=name, command=" ".join(command), status="failed", duration_s=time.perf_counter() - started, returncode=1, detail=str(exc))
|
|
96
|
-
|
|
100
|
+
combined_output = (result.stdout + "\n" + result.stderr).strip()
|
|
101
|
+
output = combined_output.splitlines()
|
|
97
102
|
return StageResult(
|
|
98
103
|
name=name,
|
|
99
104
|
command=" ".join(command),
|
|
@@ -101,4 +106,14 @@ def _run_stage(root: Path, name: str, command: list[str]) -> StageResult:
|
|
|
101
106
|
duration_s=time.perf_counter() - started,
|
|
102
107
|
returncode=result.returncode,
|
|
103
108
|
detail=output[-1] if output else "",
|
|
109
|
+
output_excerpt=_output_excerpt(combined_output) if result.returncode != 0 else "",
|
|
104
110
|
)
|
|
111
|
+
|
|
112
|
+
|
|
113
|
+
def _output_excerpt(output: str, *, max_lines: int = 80) -> str:
|
|
114
|
+
lines = output.splitlines()
|
|
115
|
+
if len(lines) <= max_lines:
|
|
116
|
+
excerpt = lines
|
|
117
|
+
else:
|
|
118
|
+
excerpt = ["... output truncated to final failing lines ...", *lines[-max_lines:]]
|
|
119
|
+
return "\n".join(f" {line}" for line in excerpt)
|
|
@@ -55,6 +55,10 @@ class LearningConfig(BaseModel):
|
|
|
55
55
|
llm_prompt_output: str = ".agentpack/learning.prompt.md"
|
|
56
56
|
pr_comment_output: str = ".agentpack/pr-learning-comment.md"
|
|
57
57
|
feedback_output: str = ".agentpack/learning-feedback.jsonl"
|
|
58
|
+
dashboard_output: str = ".agentpack/learning-dashboard.html"
|
|
59
|
+
team_lessons_output: str = ".agentpack/team-lessons.md"
|
|
60
|
+
provider_command: str = ""
|
|
61
|
+
provider_timeout_seconds: int = 60
|
|
58
62
|
inject_agent_lessons: bool = True
|
|
59
63
|
max_changed_files: int = 20
|
|
60
64
|
max_diff_chars_per_file: int = 1200
|
|
@@ -184,6 +188,10 @@ agent_lessons_output = ".agentpack/agent-lessons.md"
|
|
|
184
188
|
llm_prompt_output = ".agentpack/learning.prompt.md"
|
|
185
189
|
pr_comment_output = ".agentpack/pr-learning-comment.md"
|
|
186
190
|
feedback_output = ".agentpack/learning-feedback.jsonl"
|
|
191
|
+
dashboard_output = ".agentpack/learning-dashboard.html"
|
|
192
|
+
team_lessons_output = ".agentpack/team-lessons.md"
|
|
193
|
+
provider_command = ""
|
|
194
|
+
provider_timeout_seconds = 60
|
|
187
195
|
inject_agent_lessons = true
|
|
188
196
|
max_changed_files = 20
|
|
189
197
|
max_diff_chars_per_file = 1200
|
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
from agentpack.learning.models import (
|
|
2
2
|
AgentLesson,
|
|
3
|
+
FeedbackSignal,
|
|
4
|
+
FeedbackSummary,
|
|
3
5
|
LearningCard,
|
|
4
6
|
LearningOptions,
|
|
5
7
|
LearningReport,
|
|
@@ -11,6 +13,8 @@ from agentpack.learning.models import (
|
|
|
11
13
|
|
|
12
14
|
__all__ = [
|
|
13
15
|
"AgentLesson",
|
|
16
|
+
"FeedbackSignal",
|
|
17
|
+
"FeedbackSummary",
|
|
14
18
|
"LearningCard",
|
|
15
19
|
"LearningOptions",
|
|
16
20
|
"LearningReport",
|
|
@@ -0,0 +1,171 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
import json
|
|
4
|
+
from datetime import datetime, timezone
|
|
5
|
+
from pathlib import Path
|
|
6
|
+
|
|
7
|
+
from pydantic import ValidationError
|
|
8
|
+
|
|
9
|
+
from agentpack.learning.models import FeedbackSignal, FeedbackSummary, LearningReport
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
def record_learning_feedback(
|
|
13
|
+
path: Path,
|
|
14
|
+
report: LearningReport,
|
|
15
|
+
feedback: str,
|
|
16
|
+
note: str = "",
|
|
17
|
+
target: str = "",
|
|
18
|
+
) -> None:
|
|
19
|
+
path.parent.mkdir(parents=True, exist_ok=True)
|
|
20
|
+
payload = FeedbackSignal(
|
|
21
|
+
created_at=datetime.now(timezone.utc).isoformat(),
|
|
22
|
+
task=report.task,
|
|
23
|
+
scope=report.scope,
|
|
24
|
+
feedback=feedback,
|
|
25
|
+
note=note,
|
|
26
|
+
target=target,
|
|
27
|
+
concepts=report.concepts,
|
|
28
|
+
source_files=[source.path for source in report.source_files],
|
|
29
|
+
).model_dump(mode="json")
|
|
30
|
+
with path.open("a", encoding="utf-8") as fh:
|
|
31
|
+
fh.write(json.dumps(payload, sort_keys=True) + "\n")
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
def load_feedback_summary(path: Path) -> FeedbackSummary:
|
|
35
|
+
summary = FeedbackSummary()
|
|
36
|
+
if not path.exists():
|
|
37
|
+
return summary
|
|
38
|
+
for line in path.read_text(encoding="utf-8").splitlines():
|
|
39
|
+
if not line.strip():
|
|
40
|
+
continue
|
|
41
|
+
try:
|
|
42
|
+
signal = FeedbackSignal.model_validate_json(line)
|
|
43
|
+
except (ValidationError, ValueError):
|
|
44
|
+
continue
|
|
45
|
+
_apply_signal(summary, signal)
|
|
46
|
+
return summary
|
|
47
|
+
|
|
48
|
+
|
|
49
|
+
def apply_feedback_to_report(report: LearningReport, summary: FeedbackSummary) -> LearningReport:
|
|
50
|
+
if not _has_feedback(summary):
|
|
51
|
+
return report
|
|
52
|
+
|
|
53
|
+
rename_map = {old.lower(): new for old, new in summary.skill_renames.items()}
|
|
54
|
+
merge_map = {old.lower(): new for old, new in summary.skill_merges.items()}
|
|
55
|
+
suppressed_skills = {skill.lower() for skill in summary.suppressed_skills}
|
|
56
|
+
not_helpful = {concept.lower() for concept in summary.not_helpful_concepts}
|
|
57
|
+
suppressed = suppressed_skills | not_helpful
|
|
58
|
+
|
|
59
|
+
for source in report.source_files:
|
|
60
|
+
source.concepts = [_normalize_concept(concept, rename_map, merge_map) for concept in source.concepts]
|
|
61
|
+
source.concepts = [concept for concept in source.concepts if concept.lower() not in suppressed]
|
|
62
|
+
|
|
63
|
+
report.concepts = [_normalize_concept(concept, rename_map, merge_map) for concept in report.concepts]
|
|
64
|
+
report.concepts = _dedupe([concept for concept in report.concepts if concept.lower() not in suppressed])
|
|
65
|
+
|
|
66
|
+
report.learning_cards = [
|
|
67
|
+
card
|
|
68
|
+
for card in report.learning_cards
|
|
69
|
+
if card.title.lower() not in suppressed
|
|
70
|
+
and not any(concept.lower() == card.title.lower() for concept in summary.not_helpful_concepts)
|
|
71
|
+
]
|
|
72
|
+
for card in report.learning_cards:
|
|
73
|
+
normalized = _normalize_concept(card.title, rename_map, merge_map)
|
|
74
|
+
if normalized != card.title:
|
|
75
|
+
card.title = normalized
|
|
76
|
+
if card.title.lower() in {item.lower() for item in summary.helpful_concepts}:
|
|
77
|
+
card.body = f"{card.body} Prior feedback marked this concept useful; keep practicing it with changed-file evidence."
|
|
78
|
+
|
|
79
|
+
report.agent_lessons = [
|
|
80
|
+
lesson
|
|
81
|
+
for lesson in report.agent_lessons
|
|
82
|
+
if not _lesson_suppressed(lesson.rule, summary)
|
|
83
|
+
]
|
|
84
|
+
for lesson in report.agent_lessons:
|
|
85
|
+
if _lesson_helpful(lesson.rule, report.concepts, summary):
|
|
86
|
+
lesson.status = "accepted"
|
|
87
|
+
lesson.confidence = min(100, max(lesson.confidence, 90))
|
|
88
|
+
|
|
89
|
+
for item in report.skill_evidence:
|
|
90
|
+
item.skill = _normalize_concept(item.skill, rename_map, merge_map)
|
|
91
|
+
if item.skill.lower() in {concept.lower() for concept in summary.helpful_concepts}:
|
|
92
|
+
item.confidence = min(100, item.confidence + 10)
|
|
93
|
+
report.skill_evidence = [
|
|
94
|
+
item for item in report.skill_evidence if item.skill.lower() not in suppressed
|
|
95
|
+
]
|
|
96
|
+
return report
|
|
97
|
+
|
|
98
|
+
|
|
99
|
+
def _apply_signal(summary: FeedbackSummary, signal: FeedbackSignal) -> None:
|
|
100
|
+
target = signal.target.strip()
|
|
101
|
+
target_kind, _, target_value = target.partition(":")
|
|
102
|
+
normalized_feedback = signal.feedback.strip().lower()
|
|
103
|
+
if normalized_feedback == "helpful":
|
|
104
|
+
summary.helpful_concepts.update(signal.concepts)
|
|
105
|
+
if signal.note:
|
|
106
|
+
summary.accepted_notes.append(signal.note)
|
|
107
|
+
elif normalized_feedback == "not-helpful":
|
|
108
|
+
summary.not_helpful_concepts.update(signal.concepts)
|
|
109
|
+
|
|
110
|
+
if target_kind == "skill" and target_value:
|
|
111
|
+
if normalized_feedback == "not-helpful":
|
|
112
|
+
summary.suppressed_skills.add(target_value)
|
|
113
|
+
elif normalized_feedback == "helpful":
|
|
114
|
+
summary.helpful_concepts.add(target_value)
|
|
115
|
+
elif target_kind == "lesson" and target_value and normalized_feedback == "not-helpful":
|
|
116
|
+
summary.suppressed_lesson_terms.add(target_value)
|
|
117
|
+
elif target_kind == "rename" and "=>" in target_value:
|
|
118
|
+
old, new = [part.strip() for part in target_value.split("=>", 1)]
|
|
119
|
+
if old and new:
|
|
120
|
+
summary.skill_renames[old] = new
|
|
121
|
+
elif target_kind == "merge" and "=>" in target_value:
|
|
122
|
+
old, new = [part.strip() for part in target_value.split("=>", 1)]
|
|
123
|
+
if old and new:
|
|
124
|
+
summary.skill_merges[old] = new
|
|
125
|
+
|
|
126
|
+
|
|
127
|
+
def _has_feedback(summary: FeedbackSummary) -> bool:
|
|
128
|
+
return bool(
|
|
129
|
+
summary.helpful_concepts
|
|
130
|
+
or summary.not_helpful_concepts
|
|
131
|
+
or summary.suppressed_skills
|
|
132
|
+
or summary.suppressed_lesson_terms
|
|
133
|
+
or summary.skill_renames
|
|
134
|
+
or summary.skill_merges
|
|
135
|
+
)
|
|
136
|
+
|
|
137
|
+
|
|
138
|
+
def _normalize_concept(concept: str, renames: dict[str, str], merges: dict[str, str]) -> str:
|
|
139
|
+
key = concept.lower()
|
|
140
|
+
return merges.get(key) or renames.get(key) or concept
|
|
141
|
+
|
|
142
|
+
|
|
143
|
+
def _lesson_suppressed(rule: str, summary: FeedbackSummary) -> bool:
|
|
144
|
+
lower = rule.lower()
|
|
145
|
+
return any(term.lower() in lower for term in summary.suppressed_lesson_terms)
|
|
146
|
+
|
|
147
|
+
|
|
148
|
+
def _lesson_helpful(rule: str, concepts: list[str], summary: FeedbackSummary) -> bool:
|
|
149
|
+
lower_rule = rule.lower()
|
|
150
|
+
helpful = {concept.lower() for concept in summary.helpful_concepts}
|
|
151
|
+
for concept in concepts:
|
|
152
|
+
lower_concept = concept.lower()
|
|
153
|
+
if lower_concept not in helpful:
|
|
154
|
+
continue
|
|
155
|
+
if lower_concept in lower_rule:
|
|
156
|
+
return True
|
|
157
|
+
if any(part and part in lower_rule for part in lower_concept.split()):
|
|
158
|
+
return True
|
|
159
|
+
return False
|
|
160
|
+
|
|
161
|
+
|
|
162
|
+
def _dedupe(values: list[str]) -> list[str]:
|
|
163
|
+
seen: set[str] = set()
|
|
164
|
+
result: list[str] = []
|
|
165
|
+
for value in values:
|
|
166
|
+
key = value.lower()
|
|
167
|
+
if key in seen:
|
|
168
|
+
continue
|
|
169
|
+
seen.add(key)
|
|
170
|
+
result.append(value)
|
|
171
|
+
return result
|