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.
Files changed (85) hide show
  1. {agentpack_cli-0.1.14 → agentpack_cli-0.1.16}/PKG-INFO +11 -7
  2. {agentpack_cli-0.1.14 → agentpack_cli-0.1.16}/README.md +10 -6
  3. {agentpack_cli-0.1.14 → agentpack_cli-0.1.16}/pyproject.toml +1 -1
  4. {agentpack_cli-0.1.14 → agentpack_cli-0.1.16}/src/agentpack/__init__.py +1 -1
  5. {agentpack_cli-0.1.14 → agentpack_cli-0.1.16}/src/agentpack/analysis/ranking.py +75 -0
  6. {agentpack_cli-0.1.14 → agentpack_cli-0.1.16}/src/agentpack/application/pack_service.py +17 -2
  7. {agentpack_cli-0.1.14 → agentpack_cli-0.1.16}/src/agentpack/commands/doctor.py +29 -0
  8. {agentpack_cli-0.1.14 → agentpack_cli-0.1.16}/src/agentpack/commands/install.py +12 -4
  9. {agentpack_cli-0.1.14 → agentpack_cli-0.1.16}/src/agentpack/core/config.py +4 -0
  10. {agentpack_cli-0.1.14 → agentpack_cli-0.1.16}/src/agentpack/core/git.py +20 -0
  11. agentpack_cli-0.1.16/src/agentpack/installers/claude.py +229 -0
  12. {agentpack_cli-0.1.14 → agentpack_cli-0.1.16}/src/agentpack/installers/codex.py +5 -6
  13. {agentpack_cli-0.1.14 → agentpack_cli-0.1.16}/src/agentpack/installers/cursor.py +11 -8
  14. {agentpack_cli-0.1.14 → agentpack_cli-0.1.16}/src/agentpack/installers/windsurf.py +5 -3
  15. agentpack_cli-0.1.16/src/agentpack/mcp_server.py +415 -0
  16. agentpack_cli-0.1.14/src/agentpack/installers/claude.py +0 -173
  17. agentpack_cli-0.1.14/src/agentpack/mcp_server.py +0 -195
  18. {agentpack_cli-0.1.14 → agentpack_cli-0.1.16}/.gitignore +0 -0
  19. {agentpack_cli-0.1.14 → agentpack_cli-0.1.16}/LICENSE +0 -0
  20. {agentpack_cli-0.1.14 → agentpack_cli-0.1.16}/src/agentpack/adapters/__init__.py +0 -0
  21. {agentpack_cli-0.1.14 → agentpack_cli-0.1.16}/src/agentpack/adapters/antigravity.py +0 -0
  22. {agentpack_cli-0.1.14 → agentpack_cli-0.1.16}/src/agentpack/adapters/base.py +0 -0
  23. {agentpack_cli-0.1.14 → agentpack_cli-0.1.16}/src/agentpack/adapters/claude.py +0 -0
  24. {agentpack_cli-0.1.14 → agentpack_cli-0.1.16}/src/agentpack/adapters/codex.py +0 -0
  25. {agentpack_cli-0.1.14 → agentpack_cli-0.1.16}/src/agentpack/adapters/cursor.py +0 -0
  26. {agentpack_cli-0.1.14 → agentpack_cli-0.1.16}/src/agentpack/adapters/detect.py +0 -0
  27. {agentpack_cli-0.1.14 → agentpack_cli-0.1.16}/src/agentpack/adapters/generic.py +0 -0
  28. {agentpack_cli-0.1.14 → agentpack_cli-0.1.16}/src/agentpack/adapters/windsurf.py +0 -0
  29. {agentpack_cli-0.1.14 → agentpack_cli-0.1.16}/src/agentpack/analysis/__init__.py +0 -0
  30. {agentpack_cli-0.1.14 → agentpack_cli-0.1.16}/src/agentpack/analysis/dependency_graph.py +0 -0
  31. {agentpack_cli-0.1.14 → agentpack_cli-0.1.16}/src/agentpack/analysis/go_imports.py +0 -0
  32. {agentpack_cli-0.1.14 → agentpack_cli-0.1.16}/src/agentpack/analysis/java_imports.py +0 -0
  33. {agentpack_cli-0.1.14 → agentpack_cli-0.1.16}/src/agentpack/analysis/js_ts_imports.py +0 -0
  34. {agentpack_cli-0.1.14 → agentpack_cli-0.1.16}/src/agentpack/analysis/python_imports.py +0 -0
  35. {agentpack_cli-0.1.14 → agentpack_cli-0.1.16}/src/agentpack/analysis/rust_imports.py +0 -0
  36. {agentpack_cli-0.1.14 → agentpack_cli-0.1.16}/src/agentpack/analysis/symbols.py +0 -0
  37. {agentpack_cli-0.1.14 → agentpack_cli-0.1.16}/src/agentpack/analysis/tests.py +0 -0
  38. {agentpack_cli-0.1.14 → agentpack_cli-0.1.16}/src/agentpack/application/__init__.py +0 -0
  39. {agentpack_cli-0.1.14 → agentpack_cli-0.1.16}/src/agentpack/cli.py +0 -0
  40. {agentpack_cli-0.1.14 → agentpack_cli-0.1.16}/src/agentpack/commands/__init__.py +0 -0
  41. {agentpack_cli-0.1.14 → agentpack_cli-0.1.16}/src/agentpack/commands/_shared.py +0 -0
  42. {agentpack_cli-0.1.14 → agentpack_cli-0.1.16}/src/agentpack/commands/benchmark.py +0 -0
  43. {agentpack_cli-0.1.14 → agentpack_cli-0.1.16}/src/agentpack/commands/claude_cmd.py +0 -0
  44. {agentpack_cli-0.1.14 → agentpack_cli-0.1.16}/src/agentpack/commands/diff.py +0 -0
  45. {agentpack_cli-0.1.14 → agentpack_cli-0.1.16}/src/agentpack/commands/explain.py +0 -0
  46. {agentpack_cli-0.1.14 → agentpack_cli-0.1.16}/src/agentpack/commands/init.py +0 -0
  47. {agentpack_cli-0.1.14 → agentpack_cli-0.1.16}/src/agentpack/commands/mcp_cmd.py +0 -0
  48. {agentpack_cli-0.1.14 → agentpack_cli-0.1.16}/src/agentpack/commands/monitor.py +0 -0
  49. {agentpack_cli-0.1.14 → agentpack_cli-0.1.16}/src/agentpack/commands/pack.py +0 -0
  50. {agentpack_cli-0.1.14 → agentpack_cli-0.1.16}/src/agentpack/commands/scan.py +0 -0
  51. {agentpack_cli-0.1.14 → agentpack_cli-0.1.16}/src/agentpack/commands/stats.py +0 -0
  52. {agentpack_cli-0.1.14 → agentpack_cli-0.1.16}/src/agentpack/commands/status.py +0 -0
  53. {agentpack_cli-0.1.14 → agentpack_cli-0.1.16}/src/agentpack/commands/summarize.py +0 -0
  54. {agentpack_cli-0.1.14 → agentpack_cli-0.1.16}/src/agentpack/commands/watch.py +0 -0
  55. {agentpack_cli-0.1.14 → agentpack_cli-0.1.16}/src/agentpack/core/__init__.py +0 -0
  56. {agentpack_cli-0.1.14 → agentpack_cli-0.1.16}/src/agentpack/core/bootstrap.py +0 -0
  57. {agentpack_cli-0.1.14 → agentpack_cli-0.1.16}/src/agentpack/core/cache.py +0 -0
  58. {agentpack_cli-0.1.14 → agentpack_cli-0.1.16}/src/agentpack/core/context_pack.py +0 -0
  59. {agentpack_cli-0.1.14 → agentpack_cli-0.1.16}/src/agentpack/core/diff.py +0 -0
  60. {agentpack_cli-0.1.14 → agentpack_cli-0.1.16}/src/agentpack/core/git_hooks.py +0 -0
  61. {agentpack_cli-0.1.14 → agentpack_cli-0.1.16}/src/agentpack/core/global_install.py +0 -0
  62. {agentpack_cli-0.1.14 → agentpack_cli-0.1.16}/src/agentpack/core/ignore.py +0 -0
  63. {agentpack_cli-0.1.14 → agentpack_cli-0.1.16}/src/agentpack/core/merkle.py +0 -0
  64. {agentpack_cli-0.1.14 → agentpack_cli-0.1.16}/src/agentpack/core/models.py +0 -0
  65. {agentpack_cli-0.1.14 → agentpack_cli-0.1.16}/src/agentpack/core/redactor.py +0 -0
  66. {agentpack_cli-0.1.14 → agentpack_cli-0.1.16}/src/agentpack/core/scanner.py +0 -0
  67. {agentpack_cli-0.1.14 → agentpack_cli-0.1.16}/src/agentpack/core/snapshot.py +0 -0
  68. {agentpack_cli-0.1.14 → agentpack_cli-0.1.16}/src/agentpack/core/token_estimator.py +0 -0
  69. {agentpack_cli-0.1.14 → agentpack_cli-0.1.16}/src/agentpack/core/vscode_tasks.py +0 -0
  70. {agentpack_cli-0.1.14 → agentpack_cli-0.1.16}/src/agentpack/data/agentpack.md +0 -0
  71. {agentpack_cli-0.1.14 → agentpack_cli-0.1.16}/src/agentpack/installers/__init__.py +0 -0
  72. {agentpack_cli-0.1.14 → agentpack_cli-0.1.16}/src/agentpack/installers/antigravity.py +0 -0
  73. {agentpack_cli-0.1.14 → agentpack_cli-0.1.16}/src/agentpack/integrations/__init__.py +0 -0
  74. {agentpack_cli-0.1.14 → agentpack_cli-0.1.16}/src/agentpack/integrations/git_hooks.py +0 -0
  75. {agentpack_cli-0.1.14 → agentpack_cli-0.1.16}/src/agentpack/integrations/global_install.py +0 -0
  76. {agentpack_cli-0.1.14 → agentpack_cli-0.1.16}/src/agentpack/integrations/vscode_tasks.py +0 -0
  77. {agentpack_cli-0.1.14 → agentpack_cli-0.1.16}/src/agentpack/renderers/__init__.py +0 -0
  78. {agentpack_cli-0.1.14 → agentpack_cli-0.1.16}/src/agentpack/renderers/compact.py +0 -0
  79. {agentpack_cli-0.1.14 → agentpack_cli-0.1.16}/src/agentpack/renderers/markdown.py +0 -0
  80. {agentpack_cli-0.1.14 → agentpack_cli-0.1.16}/src/agentpack/renderers/receipts.py +0 -0
  81. {agentpack_cli-0.1.14 → agentpack_cli-0.1.16}/src/agentpack/session/__init__.py +0 -0
  82. {agentpack_cli-0.1.14 → agentpack_cli-0.1.16}/src/agentpack/session/state.py +0 -0
  83. {agentpack_cli-0.1.14 → agentpack_cli-0.1.16}/src/agentpack/summaries/__init__.py +0 -0
  84. {agentpack_cli-0.1.14 → agentpack_cli-0.1.16}/src/agentpack/summaries/base.py +0 -0
  85. {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.14
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
  [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT)
45
45
  [![CI](https://github.com/vishal2612200/agentpack/actions/workflows/ci.yml/badge.svg)](https://github.com/vishal2612200/agentpack/actions/workflows/ci.yml)
46
46
 
47
- > **Status: alpha (v0.1.11).** 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.
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`: auto-repacks when stale, injects context once per session
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 (snapshot hash, ~1ms when fresh) | ✅ git hooks | ✅ git hooks | ✅ git hooks | ✅ git hooks |
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.11. `agentpack init` bootstraps the session automatically. Use `agentpack watch` to keep context current. To change the task, edit `.agentpack/task.md`.
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 and token statistics for the last pack.
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
  [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT)
6
6
  [![CI](https://github.com/vishal2612200/agentpack/actions/workflows/ci.yml/badge.svg)](https://github.com/vishal2612200/agentpack/actions/workflows/ci.yml)
7
7
 
8
- > **Status: alpha (v0.1.11).** 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.
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`: auto-repacks when stale, injects context once per session
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 (snapshot hash, ~1ms when fresh) | ✅ git hooks | ✅ git hooks | ✅ git hooks | ✅ git hooks |
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.11. `agentpack init` bootstraps the session automatically. Use `agentpack watch` to keep context current. To change the task, edit `.agentpack/task.md`.
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 and token statistics for the last pack.
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
  ```
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "agentpack-cli"
3
- version = "0.1.14"
3
+ version = "0.1.16"
4
4
  description = "Token-aware context packing for AI coding agents — Claude, Cursor, Windsurf, and Codex"
5
5
  readme = "README.md"
6
6
  requires-python = ">=3.10"
@@ -1,3 +1,3 @@
1
1
  """AgentPack — token-aware context packing for AI coding agents."""
2
2
 
3
- __version__ = "0.1.14"
3
+ __version__ = "0.1.17"
@@ -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
- _print_auto_repack_results(installer.install_auto_repack(root))
78
- console.print(" Run [bold]agentpack pack --task \"<task>\"[/] to generate context.")
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
- hook_action = ClaudeInstaller().patch_claude_settings(root, global_install=True)
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