agentpack-cli 0.1.23__tar.gz → 0.1.25__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.25/.gitignore +33 -0
- {agentpack_cli-0.1.23 → agentpack_cli-0.1.25}/PKG-INFO +10 -3
- {agentpack_cli-0.1.23 → agentpack_cli-0.1.25}/README.md +9 -2
- {agentpack_cli-0.1.23 → agentpack_cli-0.1.25}/pyproject.toml +1 -1
- {agentpack_cli-0.1.23 → agentpack_cli-0.1.25}/src/agentpack/__init__.py +1 -1
- {agentpack_cli-0.1.23 → agentpack_cli-0.1.25}/src/agentpack/analysis/ranking.py +27 -3
- {agentpack_cli-0.1.23 → agentpack_cli-0.1.25}/src/agentpack/application/pack_service.py +172 -12
- {agentpack_cli-0.1.23 → agentpack_cli-0.1.25}/src/agentpack/commands/benchmark.py +108 -19
- {agentpack_cli-0.1.23 → agentpack_cli-0.1.25}/src/agentpack/commands/claude_cmd.py +1 -0
- {agentpack_cli-0.1.23 → agentpack_cli-0.1.25}/src/agentpack/commands/diff.py +7 -1
- {agentpack_cli-0.1.23 → agentpack_cli-0.1.25}/src/agentpack/commands/doctor.py +74 -1
- {agentpack_cli-0.1.23 → agentpack_cli-0.1.25}/src/agentpack/commands/explain.py +52 -0
- {agentpack_cli-0.1.23 → agentpack_cli-0.1.25}/src/agentpack/commands/init.py +56 -0
- {agentpack_cli-0.1.23 → agentpack_cli-0.1.25}/src/agentpack/commands/pack.py +35 -8
- {agentpack_cli-0.1.23 → agentpack_cli-0.1.25}/src/agentpack/commands/scan.py +7 -1
- {agentpack_cli-0.1.23 → agentpack_cli-0.1.25}/src/agentpack/commands/stats.py +153 -2
- {agentpack_cli-0.1.23 → agentpack_cli-0.1.25}/src/agentpack/commands/status.py +7 -1
- {agentpack_cli-0.1.23 → agentpack_cli-0.1.25}/src/agentpack/commands/summarize.py +7 -1
- {agentpack_cli-0.1.23 → agentpack_cli-0.1.25}/src/agentpack/commands/watch.py +1 -0
- {agentpack_cli-0.1.23 → agentpack_cli-0.1.25}/src/agentpack/core/context_pack.py +16 -1
- {agentpack_cli-0.1.23 → agentpack_cli-0.1.25}/src/agentpack/core/git.py +32 -0
- {agentpack_cli-0.1.23 → agentpack_cli-0.1.25}/src/agentpack/core/models.py +4 -1
- {agentpack_cli-0.1.23 → agentpack_cli-0.1.25}/src/agentpack/core/scanner.py +4 -0
- {agentpack_cli-0.1.23 → agentpack_cli-0.1.25}/src/agentpack/mcp_server.py +33 -1
- {agentpack_cli-0.1.23 → agentpack_cli-0.1.25}/src/agentpack/renderers/markdown.py +20 -0
- {agentpack_cli-0.1.23 → agentpack_cli-0.1.25}/src/agentpack/session/state.py +2 -0
- agentpack_cli-0.1.23/.gitignore +0 -21
- {agentpack_cli-0.1.23 → agentpack_cli-0.1.25}/LICENSE +0 -0
- {agentpack_cli-0.1.23 → agentpack_cli-0.1.25}/src/agentpack/adapters/__init__.py +0 -0
- {agentpack_cli-0.1.23 → agentpack_cli-0.1.25}/src/agentpack/adapters/antigravity.py +0 -0
- {agentpack_cli-0.1.23 → agentpack_cli-0.1.25}/src/agentpack/adapters/base.py +0 -0
- {agentpack_cli-0.1.23 → agentpack_cli-0.1.25}/src/agentpack/adapters/claude.py +0 -0
- {agentpack_cli-0.1.23 → agentpack_cli-0.1.25}/src/agentpack/adapters/codex.py +0 -0
- {agentpack_cli-0.1.23 → agentpack_cli-0.1.25}/src/agentpack/adapters/cursor.py +0 -0
- {agentpack_cli-0.1.23 → agentpack_cli-0.1.25}/src/agentpack/adapters/detect.py +0 -0
- {agentpack_cli-0.1.23 → agentpack_cli-0.1.25}/src/agentpack/adapters/generic.py +0 -0
- {agentpack_cli-0.1.23 → agentpack_cli-0.1.25}/src/agentpack/adapters/windsurf.py +0 -0
- {agentpack_cli-0.1.23 → agentpack_cli-0.1.25}/src/agentpack/analysis/__init__.py +0 -0
- {agentpack_cli-0.1.23 → agentpack_cli-0.1.25}/src/agentpack/analysis/dependency_graph.py +0 -0
- {agentpack_cli-0.1.23 → agentpack_cli-0.1.25}/src/agentpack/analysis/go_imports.py +0 -0
- {agentpack_cli-0.1.23 → agentpack_cli-0.1.25}/src/agentpack/analysis/java_imports.py +0 -0
- {agentpack_cli-0.1.23 → agentpack_cli-0.1.25}/src/agentpack/analysis/js_ts_imports.py +0 -0
- {agentpack_cli-0.1.23 → agentpack_cli-0.1.25}/src/agentpack/analysis/python_imports.py +0 -0
- {agentpack_cli-0.1.23 → agentpack_cli-0.1.25}/src/agentpack/analysis/rust_imports.py +0 -0
- {agentpack_cli-0.1.23 → agentpack_cli-0.1.25}/src/agentpack/analysis/symbols.py +0 -0
- {agentpack_cli-0.1.23 → agentpack_cli-0.1.25}/src/agentpack/analysis/tests.py +0 -0
- {agentpack_cli-0.1.23 → agentpack_cli-0.1.25}/src/agentpack/application/__init__.py +0 -0
- {agentpack_cli-0.1.23 → agentpack_cli-0.1.25}/src/agentpack/cli.py +0 -0
- {agentpack_cli-0.1.23 → agentpack_cli-0.1.25}/src/agentpack/commands/__init__.py +0 -0
- {agentpack_cli-0.1.23 → agentpack_cli-0.1.25}/src/agentpack/commands/_shared.py +0 -0
- {agentpack_cli-0.1.23 → agentpack_cli-0.1.25}/src/agentpack/commands/hook_cmd.py +0 -0
- {agentpack_cli-0.1.23 → agentpack_cli-0.1.25}/src/agentpack/commands/install.py +0 -0
- {agentpack_cli-0.1.23 → agentpack_cli-0.1.25}/src/agentpack/commands/mcp_cmd.py +0 -0
- {agentpack_cli-0.1.23 → agentpack_cli-0.1.25}/src/agentpack/commands/monitor.py +0 -0
- {agentpack_cli-0.1.23 → agentpack_cli-0.1.25}/src/agentpack/commands/quickstart.py +0 -0
- {agentpack_cli-0.1.23 → agentpack_cli-0.1.25}/src/agentpack/core/__init__.py +0 -0
- {agentpack_cli-0.1.23 → agentpack_cli-0.1.25}/src/agentpack/core/bootstrap.py +0 -0
- {agentpack_cli-0.1.23 → agentpack_cli-0.1.25}/src/agentpack/core/cache.py +0 -0
- {agentpack_cli-0.1.23 → agentpack_cli-0.1.25}/src/agentpack/core/config.py +0 -0
- {agentpack_cli-0.1.23 → agentpack_cli-0.1.25}/src/agentpack/core/diff.py +0 -0
- {agentpack_cli-0.1.23 → agentpack_cli-0.1.25}/src/agentpack/core/git_hooks.py +0 -0
- {agentpack_cli-0.1.23 → agentpack_cli-0.1.25}/src/agentpack/core/global_install.py +0 -0
- {agentpack_cli-0.1.23 → agentpack_cli-0.1.25}/src/agentpack/core/ignore.py +0 -0
- {agentpack_cli-0.1.23 → agentpack_cli-0.1.25}/src/agentpack/core/merkle.py +0 -0
- {agentpack_cli-0.1.23 → agentpack_cli-0.1.25}/src/agentpack/core/redactor.py +0 -0
- {agentpack_cli-0.1.23 → agentpack_cli-0.1.25}/src/agentpack/core/snapshot.py +0 -0
- {agentpack_cli-0.1.23 → agentpack_cli-0.1.25}/src/agentpack/core/token_estimator.py +0 -0
- {agentpack_cli-0.1.23 → agentpack_cli-0.1.25}/src/agentpack/core/vscode_tasks.py +0 -0
- {agentpack_cli-0.1.23 → agentpack_cli-0.1.25}/src/agentpack/data/agentpack.md +0 -0
- {agentpack_cli-0.1.23 → agentpack_cli-0.1.25}/src/agentpack/installers/__init__.py +0 -0
- {agentpack_cli-0.1.23 → agentpack_cli-0.1.25}/src/agentpack/installers/antigravity.py +0 -0
- {agentpack_cli-0.1.23 → agentpack_cli-0.1.25}/src/agentpack/installers/claude.py +0 -0
- {agentpack_cli-0.1.23 → agentpack_cli-0.1.25}/src/agentpack/installers/codex.py +0 -0
- {agentpack_cli-0.1.23 → agentpack_cli-0.1.25}/src/agentpack/installers/cursor.py +0 -0
- {agentpack_cli-0.1.23 → agentpack_cli-0.1.25}/src/agentpack/installers/windsurf.py +0 -0
- {agentpack_cli-0.1.23 → agentpack_cli-0.1.25}/src/agentpack/integrations/__init__.py +0 -0
- {agentpack_cli-0.1.23 → agentpack_cli-0.1.25}/src/agentpack/integrations/git_hooks.py +0 -0
- {agentpack_cli-0.1.23 → agentpack_cli-0.1.25}/src/agentpack/integrations/global_install.py +0 -0
- {agentpack_cli-0.1.23 → agentpack_cli-0.1.25}/src/agentpack/integrations/vscode_tasks.py +0 -0
- {agentpack_cli-0.1.23 → agentpack_cli-0.1.25}/src/agentpack/renderers/__init__.py +0 -0
- {agentpack_cli-0.1.23 → agentpack_cli-0.1.25}/src/agentpack/renderers/compact.py +0 -0
- {agentpack_cli-0.1.23 → agentpack_cli-0.1.25}/src/agentpack/renderers/receipts.py +0 -0
- {agentpack_cli-0.1.23 → agentpack_cli-0.1.25}/src/agentpack/session/__init__.py +0 -0
- {agentpack_cli-0.1.23 → agentpack_cli-0.1.25}/src/agentpack/summaries/__init__.py +0 -0
- {agentpack_cli-0.1.23 → agentpack_cli-0.1.25}/src/agentpack/summaries/base.py +0 -0
- {agentpack_cli-0.1.23 → agentpack_cli-0.1.25}/src/agentpack/summaries/offline.py +0 -0
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
__pycache__/
|
|
2
|
+
*.py[cod]
|
|
3
|
+
*.egg-info/
|
|
4
|
+
dist/
|
|
5
|
+
build/
|
|
6
|
+
.eggs/
|
|
7
|
+
*.egg
|
|
8
|
+
|
|
9
|
+
.venv/
|
|
10
|
+
venv/
|
|
11
|
+
env/
|
|
12
|
+
|
|
13
|
+
# agentpack:start
|
|
14
|
+
# AgentPack generated context/cache (safe to ignore)
|
|
15
|
+
.agentpack/cache/
|
|
16
|
+
.agentpack/snapshots/
|
|
17
|
+
.agentpack/context*
|
|
18
|
+
.agentpack/metrics.jsonl
|
|
19
|
+
.agentpack/pack_metadata.json
|
|
20
|
+
.agentpack/activity.log
|
|
21
|
+
.agentpack/.gitignore
|
|
22
|
+
.agentpack/.mcp_reminded
|
|
23
|
+
.agentpack/session.json
|
|
24
|
+
.agentpack/task.md
|
|
25
|
+
.agentpack/benchmark_results.jsonl
|
|
26
|
+
.agent/skills/agentpack/
|
|
27
|
+
# agentpack:end
|
|
28
|
+
|
|
29
|
+
.pytest_cache/
|
|
30
|
+
.mypy_cache/
|
|
31
|
+
.ruff_cache/
|
|
32
|
+
|
|
33
|
+
*.dist-info/
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: agentpack-cli
|
|
3
|
-
Version: 0.1.
|
|
3
|
+
Version: 0.1.25
|
|
4
4
|
Summary: Task-aware context packing for AI coding agents — Claude, Cursor, Windsurf, Codex, and Antigravity
|
|
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.25).** 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
|
|
|
@@ -642,6 +642,7 @@ agentpack init --share-cache # commit cache/ to git for team sharing
|
|
|
642
642
|
|
|
643
643
|
Creates:
|
|
644
644
|
```
|
|
645
|
+
.gitignore # patched idempotently with AgentPack generated artifacts
|
|
645
646
|
.agentignore # gitignore-style file exclusion rules
|
|
646
647
|
.agentpack/
|
|
647
648
|
config.toml # configuration (safe to commit)
|
|
@@ -896,6 +897,7 @@ Mode comparison: fix auth token expiry
|
|
|
896
897
|
[[cases]]
|
|
897
898
|
task = "fix auth token expiry"
|
|
898
899
|
mode = "balanced"
|
|
900
|
+
task_type = "backend-api"
|
|
899
901
|
expected_files = [
|
|
900
902
|
"src/auth/token.py",
|
|
901
903
|
"src/auth/session.py",
|
|
@@ -909,6 +911,8 @@ expected_files = [
|
|
|
909
911
|
|
|
910
912
|
Use `--misses` when recall is low. It prints each expected file that was not selected with status, rank, score, and scoring reasons, which helps separate ignored files, budget cuts, low scores, and missing dependency signals.
|
|
911
913
|
|
|
914
|
+
Add `task_type` to group results by workflow area. Benchmark summaries report average precision, recall, F1, and token noise by type, so a repo can show "backend-api is good, frontend-web is noisy" instead of hiding that under one aggregate.
|
|
915
|
+
|
|
912
916
|
---
|
|
913
917
|
|
|
914
918
|
### `agentpack scan`
|
|
@@ -949,7 +953,7 @@ agentpack benchmark --compare --misses
|
|
|
949
953
|
|
|
950
954
|
`--sample-fixtures` runs bundled FastAPI, Next.js, and mixed Python/TypeScript fixture evals from an AgentPack source checkout. It is a smoke test, not a claim about your repo.
|
|
951
955
|
|
|
952
|
-
For an 8+ usefulness signal, use `benchmark.toml` with real third-party or customer-style repos: 5-20 historical tasks, the files actually changed for each task, and `--compare` results for recall, F1, rank@K, and token noise. That is better than trusting generic benchmarks because it tells you whether AgentPack selects the files that matter in code the package has never seen.
|
|
956
|
+
For an 8+ usefulness signal, use `benchmark.toml` with real third-party or customer-style repos: 5-20 historical tasks, `task_type` labels, the files actually changed for each task, and `--compare` results for recall, F1, rank@K, and token noise. That is better than trusting generic benchmarks because it tells you whether AgentPack selects the files that matter in code the package has never seen.
|
|
953
957
|
|
|
954
958
|
---
|
|
955
959
|
|
|
@@ -1111,8 +1115,11 @@ Works like `.gitignore`. Default rules exclude:
|
|
|
1111
1115
|
.agentignore ✓ commit
|
|
1112
1116
|
.agentpack/config.toml ✓ commit
|
|
1113
1117
|
.agentpack/cache/ ✓ commit if --share-cache (recommended for teams)
|
|
1118
|
+
.agentpack/.gitignore ✗ gitignored
|
|
1114
1119
|
.agentpack/snapshots/ ✗ gitignored
|
|
1115
1120
|
.agentpack/context.* ✗ gitignored
|
|
1121
|
+
.agentpack/task.md ✗ gitignored (local current task)
|
|
1122
|
+
.agent/skills/agentpack/ ✗ gitignored (generated Antigravity context)
|
|
1116
1123
|
```
|
|
1117
1124
|
|
|
1118
1125
|
---
|
|
@@ -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.25).** 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
|
|
|
@@ -603,6 +603,7 @@ agentpack init --share-cache # commit cache/ to git for team sharing
|
|
|
603
603
|
|
|
604
604
|
Creates:
|
|
605
605
|
```
|
|
606
|
+
.gitignore # patched idempotently with AgentPack generated artifacts
|
|
606
607
|
.agentignore # gitignore-style file exclusion rules
|
|
607
608
|
.agentpack/
|
|
608
609
|
config.toml # configuration (safe to commit)
|
|
@@ -857,6 +858,7 @@ Mode comparison: fix auth token expiry
|
|
|
857
858
|
[[cases]]
|
|
858
859
|
task = "fix auth token expiry"
|
|
859
860
|
mode = "balanced"
|
|
861
|
+
task_type = "backend-api"
|
|
860
862
|
expected_files = [
|
|
861
863
|
"src/auth/token.py",
|
|
862
864
|
"src/auth/session.py",
|
|
@@ -870,6 +872,8 @@ expected_files = [
|
|
|
870
872
|
|
|
871
873
|
Use `--misses` when recall is low. It prints each expected file that was not selected with status, rank, score, and scoring reasons, which helps separate ignored files, budget cuts, low scores, and missing dependency signals.
|
|
872
874
|
|
|
875
|
+
Add `task_type` to group results by workflow area. Benchmark summaries report average precision, recall, F1, and token noise by type, so a repo can show "backend-api is good, frontend-web is noisy" instead of hiding that under one aggregate.
|
|
876
|
+
|
|
873
877
|
---
|
|
874
878
|
|
|
875
879
|
### `agentpack scan`
|
|
@@ -910,7 +914,7 @@ agentpack benchmark --compare --misses
|
|
|
910
914
|
|
|
911
915
|
`--sample-fixtures` runs bundled FastAPI, Next.js, and mixed Python/TypeScript fixture evals from an AgentPack source checkout. It is a smoke test, not a claim about your repo.
|
|
912
916
|
|
|
913
|
-
For an 8+ usefulness signal, use `benchmark.toml` with real third-party or customer-style repos: 5-20 historical tasks, the files actually changed for each task, and `--compare` results for recall, F1, rank@K, and token noise. That is better than trusting generic benchmarks because it tells you whether AgentPack selects the files that matter in code the package has never seen.
|
|
917
|
+
For an 8+ usefulness signal, use `benchmark.toml` with real third-party or customer-style repos: 5-20 historical tasks, `task_type` labels, the files actually changed for each task, and `--compare` results for recall, F1, rank@K, and token noise. That is better than trusting generic benchmarks because it tells you whether AgentPack selects the files that matter in code the package has never seen.
|
|
914
918
|
|
|
915
919
|
---
|
|
916
920
|
|
|
@@ -1072,8 +1076,11 @@ Works like `.gitignore`. Default rules exclude:
|
|
|
1072
1076
|
.agentignore ✓ commit
|
|
1073
1077
|
.agentpack/config.toml ✓ commit
|
|
1074
1078
|
.agentpack/cache/ ✓ commit if --share-cache (recommended for teams)
|
|
1079
|
+
.agentpack/.gitignore ✗ gitignored
|
|
1075
1080
|
.agentpack/snapshots/ ✗ gitignored
|
|
1076
1081
|
.agentpack/context.* ✗ gitignored
|
|
1082
|
+
.agentpack/task.md ✗ gitignored (local current task)
|
|
1083
|
+
.agent/skills/agentpack/ ✗ gitignored (generated Antigravity context)
|
|
1077
1084
|
```
|
|
1078
1085
|
|
|
1079
1086
|
---
|
|
@@ -16,6 +16,16 @@ _STOPWORDS = {
|
|
|
16
16
|
"use", "using", "used", "how", "what", "when", "where", "why",
|
|
17
17
|
}
|
|
18
18
|
|
|
19
|
+
_GENERIC_TASK_TERMS = {
|
|
20
|
+
"add", "added", "change", "changed", "changes", "clean", "cleanup",
|
|
21
|
+
"code", "commit", "context", "debug", "dev", "development", "doc",
|
|
22
|
+
"docs", "eval", "evals", "feature", "fix", "freshness", "general",
|
|
23
|
+
"impl", "implement", "implementation", "improve", "issue", "metric", "metrics",
|
|
24
|
+
"noise", "noisy", "package", "pack", "packs", "release", "repo",
|
|
25
|
+
"source", "sync", "task", "tasks", "test", "tests", "update", "use",
|
|
26
|
+
"useful", "usefulness", "version", "workflow", "workflows",
|
|
27
|
+
}
|
|
28
|
+
|
|
19
29
|
_CONCEPT_MAP: dict[str, frozenset[str]] = {
|
|
20
30
|
# rate limiting
|
|
21
31
|
"rate": frozenset({"throttle", "ratelimit", "leaky", "bucket", "debounce", "backoff", "quota"}),
|
|
@@ -219,15 +229,18 @@ def extract_keyword_weights(task: str) -> dict[str, float]:
|
|
|
219
229
|
continue
|
|
220
230
|
if word in _STOPWORDS:
|
|
221
231
|
continue
|
|
222
|
-
|
|
232
|
+
literal_weight = 0.25 if word in _GENERIC_TASK_TERMS else 1.0
|
|
233
|
+
_add_keyword_weight(keyword_weights, word, literal_weight)
|
|
223
234
|
if word in _VARIANTS:
|
|
224
|
-
|
|
235
|
+
variant = _VARIANTS[word]
|
|
236
|
+
variant_weight = 0.25 if variant in _GENERIC_TASK_TERMS else min(0.75, literal_weight)
|
|
237
|
+
_add_keyword_weight(keyword_weights, variant, variant_weight)
|
|
225
238
|
|
|
226
239
|
# Expand via concept map one level only. Expanded concepts are weaker than
|
|
227
240
|
# literal task words so broad terms like "task" do not dominate ranking.
|
|
228
241
|
expanded: dict[str, float] = {}
|
|
229
242
|
for kw in keyword_weights:
|
|
230
|
-
if kw in _CONCEPT_MAP:
|
|
243
|
+
if kw in _CONCEPT_MAP and kw not in _GENERIC_TASK_TERMS:
|
|
231
244
|
for synonym in _CONCEPT_MAP[kw]:
|
|
232
245
|
_add_keyword_weight(expanded, synonym, 0.35)
|
|
233
246
|
if synonym in _VARIANTS:
|
|
@@ -237,6 +250,17 @@ def extract_keyword_weights(task: str) -> dict[str, float]:
|
|
|
237
250
|
return keyword_weights
|
|
238
251
|
|
|
239
252
|
|
|
253
|
+
def generic_task_term_ratio(task: str) -> float:
|
|
254
|
+
words = [
|
|
255
|
+
word for word in re.split(r"[^a-zA-Z0-9]+", task.lower())
|
|
256
|
+
if len(word) >= 3 and word not in _STOPWORDS
|
|
257
|
+
]
|
|
258
|
+
if not words:
|
|
259
|
+
return 0.0
|
|
260
|
+
generic = sum(1 for word in words if word in _GENERIC_TASK_TERMS)
|
|
261
|
+
return generic / len(words)
|
|
262
|
+
|
|
263
|
+
|
|
240
264
|
def extract_keywords(task: str) -> set[str]:
|
|
241
265
|
return set(extract_keyword_weights(task))
|
|
242
266
|
|
|
@@ -16,12 +16,14 @@ 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.renderers.markdown import render_generic
|
|
19
20
|
from agentpack.analysis.ranking import (
|
|
20
21
|
score_files,
|
|
21
22
|
extract_keyword_weights,
|
|
22
23
|
enrich_keyword_weights_from_files,
|
|
23
24
|
boost_paired_tests,
|
|
24
25
|
boost_cross_layer_related,
|
|
26
|
+
generic_task_term_ratio,
|
|
25
27
|
)
|
|
26
28
|
from agentpack.analysis.tests import find_related_tests
|
|
27
29
|
from agentpack.analysis import dependency_graph as dep_graph_mod
|
|
@@ -37,6 +39,7 @@ class PackRequest:
|
|
|
37
39
|
budget: int
|
|
38
40
|
since: str | None
|
|
39
41
|
refresh: bool
|
|
42
|
+
task_source: str = "explicit"
|
|
40
43
|
|
|
41
44
|
|
|
42
45
|
@dataclass
|
|
@@ -57,6 +60,7 @@ class ChangeSet:
|
|
|
57
60
|
all_changed: set[str]
|
|
58
61
|
git_staged: set[str]
|
|
59
62
|
recently_modified: list[str]
|
|
63
|
+
source: str
|
|
60
64
|
current_snap: dict[str, Any] = field(default_factory=dict)
|
|
61
65
|
|
|
62
66
|
|
|
@@ -64,6 +68,7 @@ class ChangeSet:
|
|
|
64
68
|
class RankResult:
|
|
65
69
|
"""Result of keyword extraction and file scoring."""
|
|
66
70
|
keywords: set[str]
|
|
71
|
+
generic_ratio: float
|
|
67
72
|
scored: list[tuple[Any, float, list[str]]]
|
|
68
73
|
|
|
69
74
|
|
|
@@ -80,6 +85,8 @@ class PackPlan:
|
|
|
80
85
|
git_staged: set[str]
|
|
81
86
|
recently_modified: list[str]
|
|
82
87
|
keywords: set[str]
|
|
88
|
+
generic_task_ratio: float
|
|
89
|
+
changed_files_source: str
|
|
83
90
|
scored: list[tuple[Any, float, list[str]]]
|
|
84
91
|
selected: list[SelectedFile]
|
|
85
92
|
receipts: list[Receipt]
|
|
@@ -119,6 +126,7 @@ class ChangeDetector:
|
|
|
119
126
|
all_changed=changed_from_snap | git_changed,
|
|
120
127
|
git_staged=git_staged,
|
|
121
128
|
recently_modified=recently_modified,
|
|
129
|
+
source=_change_source(root, since, changed_from_snap, git_changed),
|
|
122
130
|
current_snap=current_snap,
|
|
123
131
|
)
|
|
124
132
|
|
|
@@ -140,6 +148,7 @@ class FileRanker:
|
|
|
140
148
|
keyword_weights = extract_keyword_weights(task)
|
|
141
149
|
keyword_weights = enrich_keyword_weights_from_files(keyword_weights, changes.all_changed, packable)
|
|
142
150
|
keywords = set(keyword_weights)
|
|
151
|
+
generic_ratio = generic_task_term_ratio(task)
|
|
143
152
|
all_paths = {f.path for f in packable}
|
|
144
153
|
|
|
145
154
|
for fi in packable:
|
|
@@ -165,7 +174,7 @@ class FileRanker:
|
|
|
165
174
|
)
|
|
166
175
|
scored = boost_cross_layer_related(scored, keyword_weights, weights=cfg.scoring)
|
|
167
176
|
scored = boost_paired_tests(scored, weights=cfg.scoring)
|
|
168
|
-
return RankResult(keywords=keywords, scored=scored)
|
|
177
|
+
return RankResult(keywords=keywords, generic_ratio=generic_ratio, scored=scored)
|
|
169
178
|
|
|
170
179
|
|
|
171
180
|
class PackPlanner:
|
|
@@ -185,6 +194,7 @@ class PackPlanner:
|
|
|
185
194
|
previous_snapshot=previous_snap,
|
|
186
195
|
include_globs=cfg.project.include_globs or None,
|
|
187
196
|
exclude_globs=cfg.project.exclude_globs or None,
|
|
197
|
+
always_skip_paths=AdapterRegistry.generated_output_paths(root, cfg),
|
|
188
198
|
)
|
|
189
199
|
phase_times["scan"] = time.perf_counter() - t0
|
|
190
200
|
|
|
@@ -217,8 +227,8 @@ class PackPlanner:
|
|
|
217
227
|
budget=effective_budget,
|
|
218
228
|
max_file_tokens=cfg.context.max_file_tokens,
|
|
219
229
|
keywords=rank_result.keywords,
|
|
220
|
-
min_summary_score=cfg.
|
|
221
|
-
max_summary_files=_summary_cap_for_mode(cfg, request.mode),
|
|
230
|
+
min_summary_score=_summary_score_floor(cfg, rank_result.generic_ratio),
|
|
231
|
+
max_summary_files=_summary_cap_for_mode(cfg, request.mode, rank_result.generic_ratio),
|
|
222
232
|
)
|
|
223
233
|
phase_times["select"] = time.perf_counter() - t0
|
|
224
234
|
|
|
@@ -233,6 +243,8 @@ class PackPlanner:
|
|
|
233
243
|
git_staged=changes.git_staged,
|
|
234
244
|
recently_modified=changes.recently_modified,
|
|
235
245
|
keywords=rank_result.keywords,
|
|
246
|
+
generic_task_ratio=rank_result.generic_ratio,
|
|
247
|
+
changed_files_source=changes.source,
|
|
236
248
|
scored=rank_result.scored,
|
|
237
249
|
selected=selected,
|
|
238
250
|
receipts=receipts,
|
|
@@ -245,7 +257,7 @@ class AdapterRegistry:
|
|
|
245
257
|
"""Maps agent names to adapter instances; extensible without touching PackService."""
|
|
246
258
|
|
|
247
259
|
@staticmethod
|
|
248
|
-
def
|
|
260
|
+
def _factories(cfg: Any) -> dict[str, Any]:
|
|
249
261
|
from agentpack.adapters.antigravity import AntigravityAdapter
|
|
250
262
|
from agentpack.adapters.claude import ClaudeAdapter
|
|
251
263
|
from agentpack.adapters.codex import CodexAdapter
|
|
@@ -253,15 +265,33 @@ class AdapterRegistry:
|
|
|
253
265
|
from agentpack.adapters.windsurf import WindsurfAdapter
|
|
254
266
|
from agentpack.adapters.generic import GenericAdapter
|
|
255
267
|
|
|
256
|
-
|
|
268
|
+
return {
|
|
257
269
|
"antigravity": lambda: AntigravityAdapter(),
|
|
258
270
|
"claude": lambda: ClaudeAdapter(cfg.agents.claude.output),
|
|
259
271
|
"cursor": lambda: CursorAdapter(cfg.agents.generic.output),
|
|
260
272
|
"windsurf": lambda: WindsurfAdapter(cfg.agents.generic.output),
|
|
261
273
|
"codex": lambda: CodexAdapter(cfg.agents.generic.output),
|
|
274
|
+
"generic": lambda: GenericAdapter(cfg.agents.generic.output),
|
|
262
275
|
}
|
|
276
|
+
|
|
277
|
+
@staticmethod
|
|
278
|
+
def get(agent: str, cfg: Any) -> Any:
|
|
279
|
+
from agentpack.adapters.generic import GenericAdapter
|
|
280
|
+
|
|
281
|
+
adapters = AdapterRegistry._factories(cfg)
|
|
263
282
|
return adapters.get(agent, lambda: GenericAdapter(cfg.agents.generic.output))()
|
|
264
283
|
|
|
284
|
+
@staticmethod
|
|
285
|
+
def generated_output_paths(root: Path, cfg: Any) -> set[str]:
|
|
286
|
+
paths: set[str] = set()
|
|
287
|
+
for factory in AdapterRegistry._factories(cfg).values():
|
|
288
|
+
try:
|
|
289
|
+
out_path = factory().output_path(root)
|
|
290
|
+
paths.add(str(out_path.relative_to(root)).replace("\\", "/"))
|
|
291
|
+
except (OSError, ValueError):
|
|
292
|
+
continue
|
|
293
|
+
return paths
|
|
294
|
+
|
|
265
295
|
|
|
266
296
|
class PackService:
|
|
267
297
|
"""Materializes a plan from PackPlanner into a written context file."""
|
|
@@ -279,6 +309,13 @@ class PackService:
|
|
|
279
309
|
saving_pct = (1 - packed_tokens / all_tokens) * 100 if all_tokens > 0 else 0.0
|
|
280
310
|
|
|
281
311
|
all_redaction_warnings = [w for sf in plan.selected for w in sf.redaction_warnings]
|
|
312
|
+
freshness = _build_freshness_metadata(
|
|
313
|
+
root,
|
|
314
|
+
request=request,
|
|
315
|
+
plan=plan,
|
|
316
|
+
snapshot_root_hash=plan.current_snap["root_hash"],
|
|
317
|
+
)
|
|
318
|
+
freshness_warnings = _freshness_warnings(root, request, freshness)
|
|
282
319
|
|
|
283
320
|
pack_obj = ContextPack(
|
|
284
321
|
task=request.task,
|
|
@@ -294,12 +331,15 @@ class PackService:
|
|
|
294
331
|
receipts=plan.receipts if cfg.context.include_receipts else [],
|
|
295
332
|
redaction_warnings=all_redaction_warnings,
|
|
296
333
|
stale=False,
|
|
334
|
+
freshness=freshness,
|
|
335
|
+
freshness_warnings=freshness_warnings,
|
|
297
336
|
)
|
|
298
337
|
|
|
299
338
|
adapter = AdapterRegistry.get(request.agent, cfg)
|
|
300
339
|
|
|
301
340
|
t0 = time.perf_counter()
|
|
302
341
|
out_path = adapter.write(pack_obj, root)
|
|
342
|
+
_write_canonical_context(pack_obj, root, out_path)
|
|
303
343
|
plan.phase_times["render"] = time.perf_counter() - t0
|
|
304
344
|
|
|
305
345
|
save_snapshot(plan.current_snap, root)
|
|
@@ -312,6 +352,9 @@ class PackService:
|
|
|
312
352
|
mode=request.mode,
|
|
313
353
|
budget=plan.budget,
|
|
314
354
|
token_estimate=packed_tokens,
|
|
355
|
+
freshness=freshness,
|
|
356
|
+
freshness_warnings=freshness_warnings,
|
|
357
|
+
selected_files=_selected_file_metadata(plan.selected),
|
|
315
358
|
)
|
|
316
359
|
excluded_receipts = [r for r in plan.receipts if r.action == "excluded"]
|
|
317
360
|
# Budget-cut: files that scored OK but didn't fit — more useful signal than "score too low"
|
|
@@ -347,6 +390,33 @@ class PackService:
|
|
|
347
390
|
)
|
|
348
391
|
|
|
349
392
|
|
|
393
|
+
def _write_canonical_context(pack: ContextPack, root: Path, out_path: Path) -> None:
|
|
394
|
+
"""Keep .agentpack/context.md fresh even when the target agent writes elsewhere."""
|
|
395
|
+
canonical_path = root / ".agentpack" / "context.md"
|
|
396
|
+
try:
|
|
397
|
+
if out_path.resolve() == canonical_path.resolve():
|
|
398
|
+
return
|
|
399
|
+
except OSError:
|
|
400
|
+
if out_path == canonical_path:
|
|
401
|
+
return
|
|
402
|
+
canonical_path.parent.mkdir(parents=True, exist_ok=True)
|
|
403
|
+
canonical_path.write_text(render_generic(pack), encoding="utf-8")
|
|
404
|
+
|
|
405
|
+
|
|
406
|
+
def _selected_file_metadata(selected: list[SelectedFile]) -> list[dict[str, Any]]:
|
|
407
|
+
return [
|
|
408
|
+
{
|
|
409
|
+
"path": sf.path,
|
|
410
|
+
"mode": sf.include_mode,
|
|
411
|
+
"score": round(sf.score, 1),
|
|
412
|
+
"why": sf.reasons[0] if sf.reasons else "",
|
|
413
|
+
"reasons": sf.reasons,
|
|
414
|
+
"tokens": _sf_tokens(sf),
|
|
415
|
+
}
|
|
416
|
+
for sf in selected
|
|
417
|
+
]
|
|
418
|
+
|
|
419
|
+
|
|
350
420
|
def _sf_tokens(sf: SelectedFile) -> int:
|
|
351
421
|
if sf.content:
|
|
352
422
|
return estimate_tokens(sf.content)
|
|
@@ -359,14 +429,104 @@ def _sf_tokens(sf: SelectedFile) -> int:
|
|
|
359
429
|
return estimate_tokens("\n".join(parts)) if parts else 50
|
|
360
430
|
|
|
361
431
|
|
|
362
|
-
def
|
|
432
|
+
def _summary_score_floor(cfg: Any, generic_ratio: float) -> float:
|
|
433
|
+
floor = cfg.context.min_summary_score
|
|
434
|
+
if generic_ratio >= 0.5:
|
|
435
|
+
return floor + 15
|
|
436
|
+
if generic_ratio >= 0.35:
|
|
437
|
+
return floor + 8
|
|
438
|
+
return floor
|
|
439
|
+
|
|
440
|
+
|
|
441
|
+
def _summary_cap_for_mode(cfg: Any, mode: str, generic_ratio: float = 0.0) -> int:
|
|
363
442
|
if mode == "minimal":
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
443
|
+
cap = cfg.context.max_summary_files_minimal
|
|
444
|
+
elif mode == "balanced":
|
|
445
|
+
cap = cfg.context.max_summary_files_balanced
|
|
446
|
+
elif mode == "deep":
|
|
447
|
+
cap = cfg.context.max_summary_files_deep
|
|
448
|
+
else:
|
|
449
|
+
cap = 0
|
|
450
|
+
if cap > 0 and generic_ratio >= 0.5:
|
|
451
|
+
return max(8, cap // 2)
|
|
452
|
+
if cap > 0 and generic_ratio >= 0.35:
|
|
453
|
+
return max(12, int(cap * 0.75))
|
|
454
|
+
return cap
|
|
455
|
+
|
|
456
|
+
|
|
457
|
+
def _change_source(root: Path, since: str | None, snapshot_changed: set[str], git_changed: set[str]) -> str:
|
|
458
|
+
if not git.is_git_repo(root):
|
|
459
|
+
return "snapshot diff"
|
|
460
|
+
if since:
|
|
461
|
+
return f"git diff since {since} + snapshot diff"
|
|
462
|
+
if git_changed and snapshot_changed:
|
|
463
|
+
return "git working tree + snapshot diff"
|
|
464
|
+
if git_changed:
|
|
465
|
+
return "git working tree"
|
|
466
|
+
if snapshot_changed:
|
|
467
|
+
return "snapshot diff"
|
|
468
|
+
return "no live changes; ranking used task keywords and history"
|
|
469
|
+
|
|
470
|
+
|
|
471
|
+
def _task_md_body(root: Path) -> str | None:
|
|
472
|
+
task_md_path = root / ".agentpack" / "task.md"
|
|
473
|
+
if not task_md_path.exists():
|
|
474
|
+
return None
|
|
475
|
+
try:
|
|
476
|
+
content = task_md_path.read_text(encoding="utf-8").strip()
|
|
477
|
+
except OSError:
|
|
478
|
+
return None
|
|
479
|
+
lines = [ln for ln in content.splitlines() if ln.strip() and not ln.startswith("#")]
|
|
480
|
+
body = lines[0].strip() if lines else ""
|
|
481
|
+
placeholder = "Write or update the current coding task here."
|
|
482
|
+
if body and placeholder not in body:
|
|
483
|
+
return body
|
|
484
|
+
return None
|
|
485
|
+
|
|
486
|
+
|
|
487
|
+
def _build_freshness_metadata(
|
|
488
|
+
root: Path,
|
|
489
|
+
*,
|
|
490
|
+
request: PackRequest,
|
|
491
|
+
plan: PackPlan,
|
|
492
|
+
snapshot_root_hash: str,
|
|
493
|
+
) -> dict[str, Any]:
|
|
494
|
+
dirty = git.dirty_files(root) if git.is_git_repo(root) else set()
|
|
495
|
+
metadata: dict[str, Any] = {
|
|
496
|
+
"generated_at": datetime.now(timezone.utc).isoformat(),
|
|
497
|
+
"task_source": request.task_source,
|
|
498
|
+
"changed_files_source": plan.changed_files_source,
|
|
499
|
+
"snapshot_root_hash": snapshot_root_hash,
|
|
500
|
+
"generic_task_ratio": round(plan.generic_task_ratio, 3),
|
|
501
|
+
"dirty_files_count": len(dirty),
|
|
502
|
+
}
|
|
503
|
+
if git.is_git_repo(root):
|
|
504
|
+
metadata["git_sha"] = git.current_sha(root)
|
|
505
|
+
metadata["git_branch"] = git.current_branch(root)
|
|
506
|
+
if dirty:
|
|
507
|
+
metadata["dirty_files_sample"] = sorted(dirty)[:8]
|
|
508
|
+
task_md = _task_md_body(root)
|
|
509
|
+
if task_md:
|
|
510
|
+
metadata["task_md"] = task_md
|
|
511
|
+
return metadata
|
|
512
|
+
|
|
513
|
+
|
|
514
|
+
def _freshness_warnings(root: Path, request: PackRequest, freshness: dict[str, Any]) -> list[str]:
|
|
515
|
+
warnings: list[str] = []
|
|
516
|
+
task_md = freshness.get("task_md")
|
|
517
|
+
if task_md and task_md != request.task:
|
|
518
|
+
warnings.append(
|
|
519
|
+
".agentpack/task.md differs from the packed task; rerun with --task auto if task.md should win."
|
|
520
|
+
)
|
|
521
|
+
if freshness.get("changed_files_source") == "no live changes; ranking used task keywords and history":
|
|
522
|
+
warnings.append("No live changed files were detected; treat selected files as keyword-based hints.")
|
|
523
|
+
if freshness.get("generic_task_ratio", 0) >= 0.5:
|
|
524
|
+
warnings.append("Task terms are broad/generic; pack tightened weak-summary selection.")
|
|
525
|
+
saved_sha = freshness.get("git_sha")
|
|
526
|
+
current_sha = git.current_sha(root) if git.is_git_repo(root) else None
|
|
527
|
+
if saved_sha and current_sha and saved_sha != current_sha:
|
|
528
|
+
warnings.append("Git HEAD changed since this pack was generated.")
|
|
529
|
+
return warnings
|
|
370
530
|
|
|
371
531
|
|
|
372
532
|
def _load_last_record(metrics_path: Path) -> dict[str, Any] | None:
|