agentpack-cli 0.1.14__tar.gz → 0.1.16__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.1.14 → agentpack_cli-0.1.16}/PKG-INFO +11 -7
- {agentpack_cli-0.1.14 → agentpack_cli-0.1.16}/README.md +10 -6
- {agentpack_cli-0.1.14 → agentpack_cli-0.1.16}/pyproject.toml +1 -1
- {agentpack_cli-0.1.14 → agentpack_cli-0.1.16}/src/agentpack/__init__.py +1 -1
- {agentpack_cli-0.1.14 → agentpack_cli-0.1.16}/src/agentpack/analysis/ranking.py +75 -0
- {agentpack_cli-0.1.14 → agentpack_cli-0.1.16}/src/agentpack/application/pack_service.py +17 -2
- {agentpack_cli-0.1.14 → agentpack_cli-0.1.16}/src/agentpack/commands/doctor.py +29 -0
- {agentpack_cli-0.1.14 → agentpack_cli-0.1.16}/src/agentpack/commands/install.py +12 -4
- {agentpack_cli-0.1.14 → agentpack_cli-0.1.16}/src/agentpack/core/config.py +4 -0
- {agentpack_cli-0.1.14 → agentpack_cli-0.1.16}/src/agentpack/core/git.py +20 -0
- agentpack_cli-0.1.16/src/agentpack/installers/claude.py +229 -0
- {agentpack_cli-0.1.14 → agentpack_cli-0.1.16}/src/agentpack/installers/codex.py +5 -6
- {agentpack_cli-0.1.14 → agentpack_cli-0.1.16}/src/agentpack/installers/cursor.py +11 -8
- {agentpack_cli-0.1.14 → agentpack_cli-0.1.16}/src/agentpack/installers/windsurf.py +5 -3
- agentpack_cli-0.1.16/src/agentpack/mcp_server.py +415 -0
- agentpack_cli-0.1.14/src/agentpack/installers/claude.py +0 -173
- agentpack_cli-0.1.14/src/agentpack/mcp_server.py +0 -195
- {agentpack_cli-0.1.14 → agentpack_cli-0.1.16}/.gitignore +0 -0
- {agentpack_cli-0.1.14 → agentpack_cli-0.1.16}/LICENSE +0 -0
- {agentpack_cli-0.1.14 → agentpack_cli-0.1.16}/src/agentpack/adapters/__init__.py +0 -0
- {agentpack_cli-0.1.14 → agentpack_cli-0.1.16}/src/agentpack/adapters/antigravity.py +0 -0
- {agentpack_cli-0.1.14 → agentpack_cli-0.1.16}/src/agentpack/adapters/base.py +0 -0
- {agentpack_cli-0.1.14 → agentpack_cli-0.1.16}/src/agentpack/adapters/claude.py +0 -0
- {agentpack_cli-0.1.14 → agentpack_cli-0.1.16}/src/agentpack/adapters/codex.py +0 -0
- {agentpack_cli-0.1.14 → agentpack_cli-0.1.16}/src/agentpack/adapters/cursor.py +0 -0
- {agentpack_cli-0.1.14 → agentpack_cli-0.1.16}/src/agentpack/adapters/detect.py +0 -0
- {agentpack_cli-0.1.14 → agentpack_cli-0.1.16}/src/agentpack/adapters/generic.py +0 -0
- {agentpack_cli-0.1.14 → agentpack_cli-0.1.16}/src/agentpack/adapters/windsurf.py +0 -0
- {agentpack_cli-0.1.14 → agentpack_cli-0.1.16}/src/agentpack/analysis/__init__.py +0 -0
- {agentpack_cli-0.1.14 → agentpack_cli-0.1.16}/src/agentpack/analysis/dependency_graph.py +0 -0
- {agentpack_cli-0.1.14 → agentpack_cli-0.1.16}/src/agentpack/analysis/go_imports.py +0 -0
- {agentpack_cli-0.1.14 → agentpack_cli-0.1.16}/src/agentpack/analysis/java_imports.py +0 -0
- {agentpack_cli-0.1.14 → agentpack_cli-0.1.16}/src/agentpack/analysis/js_ts_imports.py +0 -0
- {agentpack_cli-0.1.14 → agentpack_cli-0.1.16}/src/agentpack/analysis/python_imports.py +0 -0
- {agentpack_cli-0.1.14 → agentpack_cli-0.1.16}/src/agentpack/analysis/rust_imports.py +0 -0
- {agentpack_cli-0.1.14 → agentpack_cli-0.1.16}/src/agentpack/analysis/symbols.py +0 -0
- {agentpack_cli-0.1.14 → agentpack_cli-0.1.16}/src/agentpack/analysis/tests.py +0 -0
- {agentpack_cli-0.1.14 → agentpack_cli-0.1.16}/src/agentpack/application/__init__.py +0 -0
- {agentpack_cli-0.1.14 → agentpack_cli-0.1.16}/src/agentpack/cli.py +0 -0
- {agentpack_cli-0.1.14 → agentpack_cli-0.1.16}/src/agentpack/commands/__init__.py +0 -0
- {agentpack_cli-0.1.14 → agentpack_cli-0.1.16}/src/agentpack/commands/_shared.py +0 -0
- {agentpack_cli-0.1.14 → agentpack_cli-0.1.16}/src/agentpack/commands/benchmark.py +0 -0
- {agentpack_cli-0.1.14 → agentpack_cli-0.1.16}/src/agentpack/commands/claude_cmd.py +0 -0
- {agentpack_cli-0.1.14 → agentpack_cli-0.1.16}/src/agentpack/commands/diff.py +0 -0
- {agentpack_cli-0.1.14 → agentpack_cli-0.1.16}/src/agentpack/commands/explain.py +0 -0
- {agentpack_cli-0.1.14 → agentpack_cli-0.1.16}/src/agentpack/commands/init.py +0 -0
- {agentpack_cli-0.1.14 → agentpack_cli-0.1.16}/src/agentpack/commands/mcp_cmd.py +0 -0
- {agentpack_cli-0.1.14 → agentpack_cli-0.1.16}/src/agentpack/commands/monitor.py +0 -0
- {agentpack_cli-0.1.14 → agentpack_cli-0.1.16}/src/agentpack/commands/pack.py +0 -0
- {agentpack_cli-0.1.14 → agentpack_cli-0.1.16}/src/agentpack/commands/scan.py +0 -0
- {agentpack_cli-0.1.14 → agentpack_cli-0.1.16}/src/agentpack/commands/stats.py +0 -0
- {agentpack_cli-0.1.14 → agentpack_cli-0.1.16}/src/agentpack/commands/status.py +0 -0
- {agentpack_cli-0.1.14 → agentpack_cli-0.1.16}/src/agentpack/commands/summarize.py +0 -0
- {agentpack_cli-0.1.14 → agentpack_cli-0.1.16}/src/agentpack/commands/watch.py +0 -0
- {agentpack_cli-0.1.14 → agentpack_cli-0.1.16}/src/agentpack/core/__init__.py +0 -0
- {agentpack_cli-0.1.14 → agentpack_cli-0.1.16}/src/agentpack/core/bootstrap.py +0 -0
- {agentpack_cli-0.1.14 → agentpack_cli-0.1.16}/src/agentpack/core/cache.py +0 -0
- {agentpack_cli-0.1.14 → agentpack_cli-0.1.16}/src/agentpack/core/context_pack.py +0 -0
- {agentpack_cli-0.1.14 → agentpack_cli-0.1.16}/src/agentpack/core/diff.py +0 -0
- {agentpack_cli-0.1.14 → agentpack_cli-0.1.16}/src/agentpack/core/git_hooks.py +0 -0
- {agentpack_cli-0.1.14 → agentpack_cli-0.1.16}/src/agentpack/core/global_install.py +0 -0
- {agentpack_cli-0.1.14 → agentpack_cli-0.1.16}/src/agentpack/core/ignore.py +0 -0
- {agentpack_cli-0.1.14 → agentpack_cli-0.1.16}/src/agentpack/core/merkle.py +0 -0
- {agentpack_cli-0.1.14 → agentpack_cli-0.1.16}/src/agentpack/core/models.py +0 -0
- {agentpack_cli-0.1.14 → agentpack_cli-0.1.16}/src/agentpack/core/redactor.py +0 -0
- {agentpack_cli-0.1.14 → agentpack_cli-0.1.16}/src/agentpack/core/scanner.py +0 -0
- {agentpack_cli-0.1.14 → agentpack_cli-0.1.16}/src/agentpack/core/snapshot.py +0 -0
- {agentpack_cli-0.1.14 → agentpack_cli-0.1.16}/src/agentpack/core/token_estimator.py +0 -0
- {agentpack_cli-0.1.14 → agentpack_cli-0.1.16}/src/agentpack/core/vscode_tasks.py +0 -0
- {agentpack_cli-0.1.14 → agentpack_cli-0.1.16}/src/agentpack/data/agentpack.md +0 -0
- {agentpack_cli-0.1.14 → agentpack_cli-0.1.16}/src/agentpack/installers/__init__.py +0 -0
- {agentpack_cli-0.1.14 → agentpack_cli-0.1.16}/src/agentpack/installers/antigravity.py +0 -0
- {agentpack_cli-0.1.14 → agentpack_cli-0.1.16}/src/agentpack/integrations/__init__.py +0 -0
- {agentpack_cli-0.1.14 → agentpack_cli-0.1.16}/src/agentpack/integrations/git_hooks.py +0 -0
- {agentpack_cli-0.1.14 → agentpack_cli-0.1.16}/src/agentpack/integrations/global_install.py +0 -0
- {agentpack_cli-0.1.14 → agentpack_cli-0.1.16}/src/agentpack/integrations/vscode_tasks.py +0 -0
- {agentpack_cli-0.1.14 → agentpack_cli-0.1.16}/src/agentpack/renderers/__init__.py +0 -0
- {agentpack_cli-0.1.14 → agentpack_cli-0.1.16}/src/agentpack/renderers/compact.py +0 -0
- {agentpack_cli-0.1.14 → agentpack_cli-0.1.16}/src/agentpack/renderers/markdown.py +0 -0
- {agentpack_cli-0.1.14 → agentpack_cli-0.1.16}/src/agentpack/renderers/receipts.py +0 -0
- {agentpack_cli-0.1.14 → agentpack_cli-0.1.16}/src/agentpack/session/__init__.py +0 -0
- {agentpack_cli-0.1.14 → agentpack_cli-0.1.16}/src/agentpack/session/state.py +0 -0
- {agentpack_cli-0.1.14 → agentpack_cli-0.1.16}/src/agentpack/summaries/__init__.py +0 -0
- {agentpack_cli-0.1.14 → agentpack_cli-0.1.16}/src/agentpack/summaries/base.py +0 -0
- {agentpack_cli-0.1.14 → agentpack_cli-0.1.16}/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.1.
|
|
3
|
+
Version: 0.1.16
|
|
4
4
|
Summary: Token-aware context packing for AI coding agents — Claude, Cursor, Windsurf, and Codex
|
|
5
5
|
License: MIT
|
|
6
6
|
License-File: LICENSE
|
|
@@ -44,7 +44,7 @@ Description-Content-Type: text/markdown
|
|
|
44
44
|
[](https://opensource.org/licenses/MIT)
|
|
45
45
|
[](https://github.com/vishal2612200/agentpack/actions/workflows/ci.yml)
|
|
46
46
|
|
|
47
|
-
> **Status: alpha (v0.1.
|
|
47
|
+
> **Status: alpha (v0.1.17).** Works, tested, used in real sessions. Python and JavaScript/TypeScript are the best-supported languages. Not yet validated across a wide range of repos. API may change before 1.0.
|
|
48
48
|
>
|
|
49
49
|
> **Platform note:** macOS and Linux are fully supported. Windows support is not yet implemented (git hooks use POSIX shell; the Claude Code session hooks use `python3`/`rm -f`). Contributions welcome.
|
|
50
50
|
|
|
@@ -297,7 +297,7 @@ Configures:
|
|
|
297
297
|
- `CLAUDE.md` — tells Claude to read the context pack before each task
|
|
298
298
|
- `.claude/settings.json` — two hooks:
|
|
299
299
|
- `SessionStart`: clears injection sentinel so first prompt gets context
|
|
300
|
-
- `UserPromptSubmit`:
|
|
300
|
+
- `UserPromptSubmit`: detects repo changes via content hash (not file mtime), triggers background repack using your prompt as the task description so keyword scoring matches the current conversation
|
|
301
301
|
|
|
302
302
|
After this, context is injected automatically into every Claude Code session. No `/agentpack` command needed — it just happens.
|
|
303
303
|
|
|
@@ -354,7 +354,7 @@ The Skill descriptor activates AgentPack automatically — no `--task` flag requ
|
|
|
354
354
|
|---|---|---|---|---|---|
|
|
355
355
|
| Config file patched | `CLAUDE.md` + `.claude/settings.json` | `.cursorrules` + `.cursor/rules/*.mdc` | `.windsurfrules` | `AGENTS.md` | `.agent/skills/agentpack/SKILL.md` + `GEMINI.md` |
|
|
356
356
|
| Auto-inject on startup | ✅ `UserPromptSubmit` hook | ✅ `alwaysApply` | ✅ rules file | ✅ `AGENTS.md` | ✅ Skill auto-activation |
|
|
357
|
-
| Auto-repack when stale | ✅ hook (
|
|
357
|
+
| Auto-repack when stale | ✅ hook (content hash via `root_hash`, ~1ms when fresh) | ✅ git hooks | ✅ git hooks | ✅ git hooks | ✅ git hooks |
|
|
358
358
|
| Manual repack shortcut | ✅ `/agentpack` slash cmd | ✅ VS Code task | ✅ VS Code task | `agentpack pack` | ✅ VS Code task |
|
|
359
359
|
|
|
360
360
|
---
|
|
@@ -640,7 +640,7 @@ Options:
|
|
|
640
640
|
|
|
641
641
|
### `agentpack session` _(removed)_
|
|
642
642
|
|
|
643
|
-
Session management was removed in v0.1.
|
|
643
|
+
Session management was removed in v0.1.12. `agentpack init` bootstraps the session automatically. Use `agentpack watch` to keep context current. To change the task, edit `.agentpack/task.md`.
|
|
644
644
|
|
|
645
645
|
---
|
|
646
646
|
|
|
@@ -826,13 +826,13 @@ Tokens after ignore: 210,000
|
|
|
826
826
|
|
|
827
827
|
### `agentpack stats`
|
|
828
828
|
|
|
829
|
-
Show session state
|
|
829
|
+
Show session state, token statistics, and selection accuracy for the last pack.
|
|
830
830
|
|
|
831
831
|
```bash
|
|
832
832
|
agentpack stats
|
|
833
833
|
```
|
|
834
834
|
|
|
835
|
-
When a session is active, shows session panel (agent, mode, started, refresh count) above token stats. Also lists top included files by score.
|
|
835
|
+
When a session is active, shows session panel (agent, mode, started, refresh count) above token stats. Also lists top included files by score and avg recall/precision/F1 over the last 10 runs.
|
|
836
836
|
|
|
837
837
|
---
|
|
838
838
|
|
|
@@ -910,8 +910,10 @@ agentpack monitor --clear
|
|
|
910
910
|
| Direct dependency of changed file | +50 |
|
|
911
911
|
| Reverse dependency | +40 |
|
|
912
912
|
| Has related tests | +35 |
|
|
913
|
+
| Knowledge/architecture doc (DECISIONS.md, ADR-*.md, ARCHITECTURE.md, docs/adr/, docs/decisions/, docs/rfcs/) | +30 |
|
|
913
914
|
| Config file | +25 |
|
|
914
915
|
| Recently modified | +20 |
|
|
916
|
+
| High churn (top 10% by commit frequency) | +15 |
|
|
915
917
|
| Large unrelated file | −50 |
|
|
916
918
|
| Ignored/binary | −100 |
|
|
917
919
|
|
|
@@ -960,8 +962,10 @@ content_keyword_max = 60
|
|
|
960
962
|
direct_dep = 50
|
|
961
963
|
reverse_dep = 40
|
|
962
964
|
related_test = 35
|
|
965
|
+
knowledge_file = 30 # DECISIONS.md, ADR-*.md, ARCHITECTURE.md, docs/adr/ etc.
|
|
963
966
|
config_file = 25
|
|
964
967
|
recently_modified = 20
|
|
968
|
+
churn_high = 15 # top 10% by commit frequency
|
|
965
969
|
large_unrelated_penalty = -50
|
|
966
970
|
ignored_penalty = -100
|
|
967
971
|
```
|
|
@@ -5,7 +5,7 @@
|
|
|
5
5
|
[](https://opensource.org/licenses/MIT)
|
|
6
6
|
[](https://github.com/vishal2612200/agentpack/actions/workflows/ci.yml)
|
|
7
7
|
|
|
8
|
-
> **Status: alpha (v0.1.
|
|
8
|
+
> **Status: alpha (v0.1.17).** Works, tested, used in real sessions. Python and JavaScript/TypeScript are the best-supported languages. Not yet validated across a wide range of repos. API may change before 1.0.
|
|
9
9
|
>
|
|
10
10
|
> **Platform note:** macOS and Linux are fully supported. Windows support is not yet implemented (git hooks use POSIX shell; the Claude Code session hooks use `python3`/`rm -f`). Contributions welcome.
|
|
11
11
|
|
|
@@ -258,7 +258,7 @@ Configures:
|
|
|
258
258
|
- `CLAUDE.md` — tells Claude to read the context pack before each task
|
|
259
259
|
- `.claude/settings.json` — two hooks:
|
|
260
260
|
- `SessionStart`: clears injection sentinel so first prompt gets context
|
|
261
|
-
- `UserPromptSubmit`:
|
|
261
|
+
- `UserPromptSubmit`: detects repo changes via content hash (not file mtime), triggers background repack using your prompt as the task description so keyword scoring matches the current conversation
|
|
262
262
|
|
|
263
263
|
After this, context is injected automatically into every Claude Code session. No `/agentpack` command needed — it just happens.
|
|
264
264
|
|
|
@@ -315,7 +315,7 @@ The Skill descriptor activates AgentPack automatically — no `--task` flag requ
|
|
|
315
315
|
|---|---|---|---|---|---|
|
|
316
316
|
| Config file patched | `CLAUDE.md` + `.claude/settings.json` | `.cursorrules` + `.cursor/rules/*.mdc` | `.windsurfrules` | `AGENTS.md` | `.agent/skills/agentpack/SKILL.md` + `GEMINI.md` |
|
|
317
317
|
| Auto-inject on startup | ✅ `UserPromptSubmit` hook | ✅ `alwaysApply` | ✅ rules file | ✅ `AGENTS.md` | ✅ Skill auto-activation |
|
|
318
|
-
| Auto-repack when stale | ✅ hook (
|
|
318
|
+
| Auto-repack when stale | ✅ hook (content hash via `root_hash`, ~1ms when fresh) | ✅ git hooks | ✅ git hooks | ✅ git hooks | ✅ git hooks |
|
|
319
319
|
| Manual repack shortcut | ✅ `/agentpack` slash cmd | ✅ VS Code task | ✅ VS Code task | `agentpack pack` | ✅ VS Code task |
|
|
320
320
|
|
|
321
321
|
---
|
|
@@ -601,7 +601,7 @@ Options:
|
|
|
601
601
|
|
|
602
602
|
### `agentpack session` _(removed)_
|
|
603
603
|
|
|
604
|
-
Session management was removed in v0.1.
|
|
604
|
+
Session management was removed in v0.1.12. `agentpack init` bootstraps the session automatically. Use `agentpack watch` to keep context current. To change the task, edit `.agentpack/task.md`.
|
|
605
605
|
|
|
606
606
|
---
|
|
607
607
|
|
|
@@ -787,13 +787,13 @@ Tokens after ignore: 210,000
|
|
|
787
787
|
|
|
788
788
|
### `agentpack stats`
|
|
789
789
|
|
|
790
|
-
Show session state
|
|
790
|
+
Show session state, token statistics, and selection accuracy for the last pack.
|
|
791
791
|
|
|
792
792
|
```bash
|
|
793
793
|
agentpack stats
|
|
794
794
|
```
|
|
795
795
|
|
|
796
|
-
When a session is active, shows session panel (agent, mode, started, refresh count) above token stats. Also lists top included files by score.
|
|
796
|
+
When a session is active, shows session panel (agent, mode, started, refresh count) above token stats. Also lists top included files by score and avg recall/precision/F1 over the last 10 runs.
|
|
797
797
|
|
|
798
798
|
---
|
|
799
799
|
|
|
@@ -871,8 +871,10 @@ agentpack monitor --clear
|
|
|
871
871
|
| Direct dependency of changed file | +50 |
|
|
872
872
|
| Reverse dependency | +40 |
|
|
873
873
|
| Has related tests | +35 |
|
|
874
|
+
| Knowledge/architecture doc (DECISIONS.md, ADR-*.md, ARCHITECTURE.md, docs/adr/, docs/decisions/, docs/rfcs/) | +30 |
|
|
874
875
|
| Config file | +25 |
|
|
875
876
|
| Recently modified | +20 |
|
|
877
|
+
| High churn (top 10% by commit frequency) | +15 |
|
|
876
878
|
| Large unrelated file | −50 |
|
|
877
879
|
| Ignored/binary | −100 |
|
|
878
880
|
|
|
@@ -921,8 +923,10 @@ content_keyword_max = 60
|
|
|
921
923
|
direct_dep = 50
|
|
922
924
|
reverse_dep = 40
|
|
923
925
|
related_test = 35
|
|
926
|
+
knowledge_file = 30 # DECISIONS.md, ADR-*.md, ARCHITECTURE.md, docs/adr/ etc.
|
|
924
927
|
config_file = 25
|
|
925
928
|
recently_modified = 20
|
|
929
|
+
churn_high = 15 # top 10% by commit frequency
|
|
926
930
|
large_unrelated_penalty = -50
|
|
927
931
|
ignored_penalty = -100
|
|
928
932
|
```
|
|
@@ -283,6 +283,7 @@ def score_files(
|
|
|
283
283
|
include_configs: bool = True,
|
|
284
284
|
weights: ScoringWeights | None = None,
|
|
285
285
|
summaries: dict | None = None,
|
|
286
|
+
churn_counts: dict[str, int] | None = None,
|
|
286
287
|
) -> list[tuple[FileInfo, float, list[str]]]:
|
|
287
288
|
from agentpack.core.models import DependencyGraph as _DG
|
|
288
289
|
if not isinstance(dep_graph, _DG):
|
|
@@ -292,6 +293,12 @@ def score_files(
|
|
|
292
293
|
results: list[tuple[FileInfo, float, list[str]]] = []
|
|
293
294
|
recently_set = set(recently_modified[:20])
|
|
294
295
|
|
|
296
|
+
churn_threshold: int | None = None
|
|
297
|
+
if churn_counts:
|
|
298
|
+
vals = sorted(churn_counts.values(), reverse=True)
|
|
299
|
+
cutoff_idx = max(0, len(vals) // 10 - 1) # top 10%
|
|
300
|
+
churn_threshold = vals[cutoff_idx] if vals else None
|
|
301
|
+
|
|
295
302
|
for fi in files:
|
|
296
303
|
if fi.ignored or fi.binary:
|
|
297
304
|
results.append((fi, w.ignored_penalty, ["ignored/binary"]))
|
|
@@ -368,10 +375,20 @@ def score_files(
|
|
|
368
375
|
score += w.config_file
|
|
369
376
|
reasons.append("config file")
|
|
370
377
|
|
|
378
|
+
if _is_knowledge_file(fi.path):
|
|
379
|
+
score += w.knowledge_file
|
|
380
|
+
reasons.append("knowledge/architecture doc")
|
|
381
|
+
|
|
371
382
|
if fi.path in recently_set:
|
|
372
383
|
score += w.recently_modified
|
|
373
384
|
reasons.append("recently modified")
|
|
374
385
|
|
|
386
|
+
if churn_counts and churn_threshold is not None:
|
|
387
|
+
count = churn_counts.get(fi.path, 0)
|
|
388
|
+
if count >= churn_threshold:
|
|
389
|
+
score += w.churn_high
|
|
390
|
+
reasons.append(f"high churn ({count} commits)")
|
|
391
|
+
|
|
375
392
|
if fi.too_large and score < 50:
|
|
376
393
|
score += w.large_unrelated_penalty
|
|
377
394
|
reasons.append("large unrelated file")
|
|
@@ -381,6 +398,43 @@ def score_files(
|
|
|
381
398
|
return results
|
|
382
399
|
|
|
383
400
|
|
|
401
|
+
def boost_paired_tests(
|
|
402
|
+
scored: list[tuple[FileInfo, float, list[str]]],
|
|
403
|
+
weights: ScoringWeights | None = None,
|
|
404
|
+
) -> list[tuple[FileInfo, float, list[str]]]:
|
|
405
|
+
"""Boost test files that pair with high-scoring source files.
|
|
406
|
+
|
|
407
|
+
Only applies to test files not already boosted by changed_paths.
|
|
408
|
+
Threshold: source must score above the median non-test score.
|
|
409
|
+
"""
|
|
410
|
+
w = weights or _DEFAULT_WEIGHTS
|
|
411
|
+
non_test_scores = [
|
|
412
|
+
score for fi, score, _ in scored
|
|
413
|
+
if not fi.ignored and not fi.binary and not _is_test_file(fi.path) and score > 0
|
|
414
|
+
]
|
|
415
|
+
if not non_test_scores:
|
|
416
|
+
return scored
|
|
417
|
+
threshold = sorted(non_test_scores)[len(non_test_scores) // 2] # median
|
|
418
|
+
|
|
419
|
+
source_scores = {
|
|
420
|
+
fi.path: score for fi, score, _ in scored
|
|
421
|
+
if not _is_test_file(fi.path) and score >= threshold
|
|
422
|
+
}
|
|
423
|
+
|
|
424
|
+
result = []
|
|
425
|
+
for fi, score, reasons in scored:
|
|
426
|
+
if _is_test_file(fi.path):
|
|
427
|
+
already_boosted = any("test for" in r for r in reasons)
|
|
428
|
+
if not already_boosted:
|
|
429
|
+
for src_path, src_score in source_scores.items():
|
|
430
|
+
if _test_matches_source(fi.path, src_path):
|
|
431
|
+
score += w.related_test
|
|
432
|
+
reasons = reasons + [f"test for high-scoring {src_path}"]
|
|
433
|
+
break
|
|
434
|
+
result.append((fi, score, reasons))
|
|
435
|
+
return result
|
|
436
|
+
|
|
437
|
+
|
|
384
438
|
def _is_test_file(path: str) -> bool:
|
|
385
439
|
p = Path(path)
|
|
386
440
|
return (
|
|
@@ -405,3 +459,24 @@ def _is_config_file(path: str) -> bool:
|
|
|
405
459
|
p.suffix.lower() in CONFIG_EXTENSIONS
|
|
406
460
|
or p.stem.lower() in CONFIG_NAMES
|
|
407
461
|
)
|
|
462
|
+
|
|
463
|
+
|
|
464
|
+
_KNOWLEDGE_NAMES = {
|
|
465
|
+
"decisions", "adr", "architecture", "contributing", "design",
|
|
466
|
+
"technical", "tradeoffs", "rfc", "proposal",
|
|
467
|
+
}
|
|
468
|
+
_KNOWLEDGE_DIRS = {"adr", "adrs", "decisions", "rfcs", "proposals", "design"}
|
|
469
|
+
|
|
470
|
+
|
|
471
|
+
def _is_knowledge_file(path: str) -> bool:
|
|
472
|
+
p = Path(path)
|
|
473
|
+
stem_lower = p.stem.lower()
|
|
474
|
+
# Match ADR-NNN.md patterns and known doc names
|
|
475
|
+
if stem_lower in _KNOWLEDGE_NAMES:
|
|
476
|
+
return p.suffix.lower() == ".md"
|
|
477
|
+
if re.match(r"adr[-_]?\d+", stem_lower):
|
|
478
|
+
return True
|
|
479
|
+
# Any .md file in a known docs dir
|
|
480
|
+
if any(part.lower() in _KNOWLEDGE_DIRS for part in p.parts):
|
|
481
|
+
return p.suffix.lower() == ".md"
|
|
482
|
+
return False
|
|
@@ -16,7 +16,7 @@ from agentpack.core import git
|
|
|
16
16
|
from agentpack.core.context_pack import select_files, save_pack_metadata
|
|
17
17
|
from agentpack.core.models import ContextPack, DependencyGraph, FileInfo, ScanResult, SelectedFile, Receipt
|
|
18
18
|
from agentpack.core.token_estimator import estimate_tokens
|
|
19
|
-
from agentpack.analysis.ranking import score_files, extract_keywords, enrich_keywords_from_files
|
|
19
|
+
from agentpack.analysis.ranking import score_files, extract_keywords, enrich_keywords_from_files, boost_paired_tests
|
|
20
20
|
from agentpack.analysis.tests import find_related_tests
|
|
21
21
|
from agentpack.analysis import dependency_graph as dep_graph_mod
|
|
22
22
|
from agentpack.summaries.base import build_all_summaries
|
|
@@ -128,7 +128,9 @@ class FileRanker:
|
|
|
128
128
|
task: str,
|
|
129
129
|
cfg: Any,
|
|
130
130
|
summaries: dict | None = None,
|
|
131
|
+
root: Path | None = None,
|
|
131
132
|
) -> RankResult:
|
|
133
|
+
from agentpack.core import git as _git
|
|
132
134
|
keywords = extract_keywords(task)
|
|
133
135
|
keywords = enrich_keywords_from_files(keywords, changes.all_changed, packable)
|
|
134
136
|
all_paths = {f.path for f in packable}
|
|
@@ -137,6 +139,10 @@ class FileRanker:
|
|
|
137
139
|
tests = find_related_tests(fi.path, all_paths)
|
|
138
140
|
dep_graph.nodes[fi.path].tests = tests
|
|
139
141
|
|
|
142
|
+
churn_counts: dict[str, int] = {}
|
|
143
|
+
if root is not None and _git.is_git_repo(root):
|
|
144
|
+
churn_counts = _git.file_churn_counts(root)
|
|
145
|
+
|
|
140
146
|
scored = score_files(
|
|
141
147
|
packable,
|
|
142
148
|
changed_paths=changes.all_changed,
|
|
@@ -148,7 +154,9 @@ class FileRanker:
|
|
|
148
154
|
include_configs=cfg.context.include_configs,
|
|
149
155
|
weights=cfg.scoring,
|
|
150
156
|
summaries=summaries,
|
|
157
|
+
churn_counts=churn_counts,
|
|
151
158
|
)
|
|
159
|
+
scored = boost_paired_tests(scored, weights=cfg.scoring)
|
|
152
160
|
return RankResult(keywords=keywords, scored=scored)
|
|
153
161
|
|
|
154
162
|
|
|
@@ -188,7 +196,7 @@ class PackPlanner:
|
|
|
188
196
|
phase_times["changes"] = time.perf_counter() - t0
|
|
189
197
|
|
|
190
198
|
t0 = time.perf_counter()
|
|
191
|
-
rank_result = FileRanker().rank(packable, changes, dep_graph, request.task, cfg, summaries=summaries)
|
|
199
|
+
rank_result = FileRanker().rank(packable, changes, dep_graph, request.task, cfg, summaries=summaries, root=root)
|
|
192
200
|
phase_times["rank"] = time.perf_counter() - t0
|
|
193
201
|
|
|
194
202
|
t0 = time.perf_counter()
|
|
@@ -295,6 +303,7 @@ class PackService:
|
|
|
295
303
|
budget=plan.budget,
|
|
296
304
|
token_estimate=packed_tokens,
|
|
297
305
|
)
|
|
306
|
+
excluded_receipts = [r for r in plan.receipts if r.action == "excluded"]
|
|
298
307
|
_record_metrics(
|
|
299
308
|
root,
|
|
300
309
|
task=request.task,
|
|
@@ -307,6 +316,8 @@ class PackService:
|
|
|
307
316
|
changed_count=len(plan.all_changed),
|
|
308
317
|
selected_paths=[sf.path for sf in plan.selected],
|
|
309
318
|
current_changed=plan.all_changed,
|
|
319
|
+
excluded_count=len(excluded_receipts),
|
|
320
|
+
excluded_paths=[r.path for r in excluded_receipts if r.reason == "score too low"][:10],
|
|
310
321
|
)
|
|
311
322
|
|
|
312
323
|
return PackResult(
|
|
@@ -396,6 +407,8 @@ def _record_metrics(
|
|
|
396
407
|
changed_count: int,
|
|
397
408
|
selected_paths: list[str],
|
|
398
409
|
current_changed: set[str],
|
|
410
|
+
excluded_count: int = 0,
|
|
411
|
+
excluded_paths: list[str] | None = None,
|
|
399
412
|
) -> None:
|
|
400
413
|
metrics_path = root / ".agentpack" / "metrics.jsonl"
|
|
401
414
|
accuracy = _compute_selection_accuracy(root, metrics_path, selected_paths, current_changed)
|
|
@@ -408,6 +421,8 @@ def _record_metrics(
|
|
|
408
421
|
"saving_pct": round(saving_pct, 1),
|
|
409
422
|
"selected_files": selected_count,
|
|
410
423
|
"changed_files": changed_count,
|
|
424
|
+
"excluded_files": excluded_count,
|
|
425
|
+
"excluded_paths": excluded_paths or [],
|
|
411
426
|
"selected_paths": selected_paths,
|
|
412
427
|
"phases": {k: round(v, 3) for k, v in phase_times.items()},
|
|
413
428
|
"total_s": round(sum(phase_times.values()), 3),
|
|
@@ -149,6 +149,35 @@ def register(app: typer.Typer) -> None:
|
|
|
149
149
|
if _local_has_hooks and not _global_has_hooks:
|
|
150
150
|
console.print(" [yellow]![/] Hooks local-only — context won't auto-inject in other repos. Run: agentpack install --agent claude --global")
|
|
151
151
|
|
|
152
|
+
# --- MCP server ---
|
|
153
|
+
console.print("\n[bold]MCP server[/]")
|
|
154
|
+
mcp_json = root / ".mcp.json"
|
|
155
|
+
global_claude_settings_for_mcp = Path.home() / ".claude" / "settings.json"
|
|
156
|
+
_local_has_mcp = False
|
|
157
|
+
_global_has_mcp = False
|
|
158
|
+
if mcp_json.exists():
|
|
159
|
+
try:
|
|
160
|
+
mcp_data = _json.loads(mcp_json.read_text())
|
|
161
|
+
if "agentpack" in mcp_data.get("mcpServers", {}):
|
|
162
|
+
console.print(f" [green]✓[/] MCP server registered (local): {mcp_json}")
|
|
163
|
+
_local_has_mcp = True
|
|
164
|
+
else:
|
|
165
|
+
console.print(" [yellow]![/] .mcp.json exists but agentpack missing — run: agentpack install --agent claude")
|
|
166
|
+
except Exception:
|
|
167
|
+
console.print(f" [yellow]![/] Could not parse {mcp_json}")
|
|
168
|
+
else:
|
|
169
|
+
console.print(" [dim]-[/] .mcp.json not present (run: agentpack install --agent claude)")
|
|
170
|
+
if global_claude_settings_for_mcp.exists():
|
|
171
|
+
try:
|
|
172
|
+
global_data = _json.loads(global_claude_settings_for_mcp.read_text())
|
|
173
|
+
if "agentpack" in global_data.get("mcpServers", {}):
|
|
174
|
+
console.print(f" [green]✓[/] MCP server registered (global): {global_claude_settings_for_mcp}")
|
|
175
|
+
_global_has_mcp = True
|
|
176
|
+
except Exception:
|
|
177
|
+
pass
|
|
178
|
+
if not _local_has_mcp and not _global_has_mcp:
|
|
179
|
+
console.print(" [yellow]![/] MCP server not registered — mcp__agentpack__* tools unavailable")
|
|
180
|
+
|
|
152
181
|
# --- Slash command ---
|
|
153
182
|
console.print("\n[bold]Slash command (/agentpack)[/]")
|
|
154
183
|
local_cmd = root / ".claude" / "commands" / "agentpack.md"
|
|
@@ -51,6 +51,10 @@ def register(app: typer.Typer) -> None:
|
|
|
51
51
|
scope = "~/.claude/settings.json" if global_install else ".claude/settings.json"
|
|
52
52
|
console.print(f"[green]{scope} {hook_action}.[/]")
|
|
53
53
|
|
|
54
|
+
mcp_action = installer.patch_mcp_server(root, global_install)
|
|
55
|
+
mcp_scope = "~/.claude/settings.json" if global_install else ".mcp.json"
|
|
56
|
+
console.print(f"[green]{mcp_scope} mcpServers.agentpack {mcp_action}.[/]")
|
|
57
|
+
|
|
54
58
|
if slash_command:
|
|
55
59
|
_install_slash_command(root, global_install)
|
|
56
60
|
|
|
@@ -74,8 +78,9 @@ def register(app: typer.Typer) -> None:
|
|
|
74
78
|
installer = CodexInstaller()
|
|
75
79
|
action = installer.patch_agents_md(root)
|
|
76
80
|
console.print(f"[green]AGENTS.md {action}.[/]")
|
|
77
|
-
|
|
78
|
-
|
|
81
|
+
if not global_install:
|
|
82
|
+
_print_auto_repack_results(installer.install_auto_repack(root))
|
|
83
|
+
console.print(" Run [bold]agentpack pack --agent codex --task \"<task>\"[/] to generate context.")
|
|
79
84
|
|
|
80
85
|
elif agent == "antigravity":
|
|
81
86
|
installer = AntigravityInstaller()
|
|
@@ -163,11 +168,14 @@ def register(app: typer.Typer) -> None:
|
|
|
163
168
|
# --- Agent-specific config ---
|
|
164
169
|
if agent == "claude":
|
|
165
170
|
if not dry_run:
|
|
166
|
-
|
|
171
|
+
inst = ClaudeInstaller()
|
|
172
|
+
hook_action = inst.patch_claude_settings(root, global_install=True)
|
|
167
173
|
console.print(f"\n[green]~/.claude/settings.json {hook_action}.[/]")
|
|
174
|
+
mcp_action = inst.patch_mcp_server(root, global_install=True)
|
|
175
|
+
console.print(f"[green]~/.claude/settings.json mcpServers.agentpack {mcp_action}.[/]")
|
|
168
176
|
_install_slash_command(root, global_install=True)
|
|
169
177
|
else:
|
|
170
|
-
console.print("\n[dim]Would patch: ~/.claude/settings.json (hooks)[/]")
|
|
178
|
+
console.print("\n[dim]Would patch: ~/.claude/settings.json (hooks + mcpServers for global)[/]")
|
|
171
179
|
console.print("[dim]Would install: ~/.claude/commands/agentpack.md (slash command)[/]")
|
|
172
180
|
|
|
173
181
|
elif agent == "cursor":
|
|
@@ -61,7 +61,9 @@ class ScoringWeights(BaseModel):
|
|
|
61
61
|
reverse_dep: float = 40
|
|
62
62
|
related_test: float = 35
|
|
63
63
|
config_file: float = 25
|
|
64
|
+
knowledge_file: float = 30
|
|
64
65
|
recently_modified: float = 20
|
|
66
|
+
churn_high: float = 15 # file appears in top 10% by churn
|
|
65
67
|
large_unrelated_penalty: float = -50
|
|
66
68
|
ignored_penalty: float = -100
|
|
67
69
|
|
|
@@ -106,7 +108,9 @@ direct_dep = 50
|
|
|
106
108
|
reverse_dep = 40
|
|
107
109
|
related_test = 35
|
|
108
110
|
config_file = 25
|
|
111
|
+
knowledge_file = 30
|
|
109
112
|
recently_modified = 20
|
|
113
|
+
churn_high = 15
|
|
110
114
|
large_unrelated_penalty = -50
|
|
111
115
|
ignored_penalty = -100
|
|
112
116
|
"""
|
|
@@ -74,6 +74,26 @@ def changed_files_since(root: Path, ref: str) -> set[str]:
|
|
|
74
74
|
return result
|
|
75
75
|
|
|
76
76
|
|
|
77
|
+
def file_churn_counts(root: Path, max_commits: int = 200) -> dict[str, int]:
|
|
78
|
+
"""Return commit count per file from the last max_commits commits.
|
|
79
|
+
|
|
80
|
+
Uses a single git log call — O(1) subprocess, not O(n files).
|
|
81
|
+
Returns empty dict if not a git repo or git unavailable.
|
|
82
|
+
"""
|
|
83
|
+
out = _run(
|
|
84
|
+
["git", "log", "--name-only", "--format=", f"-{max_commits}"],
|
|
85
|
+
root,
|
|
86
|
+
)
|
|
87
|
+
if not out:
|
|
88
|
+
return {}
|
|
89
|
+
counts: dict[str, int] = {}
|
|
90
|
+
for line in out.splitlines():
|
|
91
|
+
line = line.strip()
|
|
92
|
+
if line:
|
|
93
|
+
counts[line] = counts.get(line, 0) + 1
|
|
94
|
+
return counts
|
|
95
|
+
|
|
96
|
+
|
|
77
97
|
def infer_task_from_git(root: Path) -> str:
|
|
78
98
|
"""Infer a task description from branch name, changed files, and recent commits.
|
|
79
99
|
|