agentpack-cli 0.1.15__tar.gz → 0.1.19__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.15 → agentpack_cli-0.1.19}/PKG-INFO +11 -7
- {agentpack_cli-0.1.15 → agentpack_cli-0.1.19}/README.md +10 -6
- {agentpack_cli-0.1.15 → agentpack_cli-0.1.19}/pyproject.toml +1 -1
- {agentpack_cli-0.1.15 → agentpack_cli-0.1.19}/src/agentpack/__init__.py +1 -1
- {agentpack_cli-0.1.15 → agentpack_cli-0.1.19}/src/agentpack/application/pack_service.py +12 -0
- {agentpack_cli-0.1.15 → agentpack_cli-0.1.19}/src/agentpack/cli.py +2 -2
- {agentpack_cli-0.1.15 → agentpack_cli-0.1.19}/src/agentpack/commands/doctor.py +59 -2
- agentpack_cli-0.1.19/src/agentpack/commands/hook_cmd.py +223 -0
- {agentpack_cli-0.1.15 → agentpack_cli-0.1.19}/src/agentpack/commands/install.py +9 -2
- agentpack_cli-0.1.19/src/agentpack/installers/claude.py +200 -0
- agentpack_cli-0.1.19/src/agentpack/mcp_server.py +415 -0
- agentpack_cli-0.1.15/src/agentpack/installers/claude.py +0 -172
- agentpack_cli-0.1.15/src/agentpack/mcp_server.py +0 -195
- {agentpack_cli-0.1.15 → agentpack_cli-0.1.19}/.gitignore +0 -0
- {agentpack_cli-0.1.15 → agentpack_cli-0.1.19}/LICENSE +0 -0
- {agentpack_cli-0.1.15 → agentpack_cli-0.1.19}/src/agentpack/adapters/__init__.py +0 -0
- {agentpack_cli-0.1.15 → agentpack_cli-0.1.19}/src/agentpack/adapters/antigravity.py +0 -0
- {agentpack_cli-0.1.15 → agentpack_cli-0.1.19}/src/agentpack/adapters/base.py +0 -0
- {agentpack_cli-0.1.15 → agentpack_cli-0.1.19}/src/agentpack/adapters/claude.py +0 -0
- {agentpack_cli-0.1.15 → agentpack_cli-0.1.19}/src/agentpack/adapters/codex.py +0 -0
- {agentpack_cli-0.1.15 → agentpack_cli-0.1.19}/src/agentpack/adapters/cursor.py +0 -0
- {agentpack_cli-0.1.15 → agentpack_cli-0.1.19}/src/agentpack/adapters/detect.py +0 -0
- {agentpack_cli-0.1.15 → agentpack_cli-0.1.19}/src/agentpack/adapters/generic.py +0 -0
- {agentpack_cli-0.1.15 → agentpack_cli-0.1.19}/src/agentpack/adapters/windsurf.py +0 -0
- {agentpack_cli-0.1.15 → agentpack_cli-0.1.19}/src/agentpack/analysis/__init__.py +0 -0
- {agentpack_cli-0.1.15 → agentpack_cli-0.1.19}/src/agentpack/analysis/dependency_graph.py +0 -0
- {agentpack_cli-0.1.15 → agentpack_cli-0.1.19}/src/agentpack/analysis/go_imports.py +0 -0
- {agentpack_cli-0.1.15 → agentpack_cli-0.1.19}/src/agentpack/analysis/java_imports.py +0 -0
- {agentpack_cli-0.1.15 → agentpack_cli-0.1.19}/src/agentpack/analysis/js_ts_imports.py +0 -0
- {agentpack_cli-0.1.15 → agentpack_cli-0.1.19}/src/agentpack/analysis/python_imports.py +0 -0
- {agentpack_cli-0.1.15 → agentpack_cli-0.1.19}/src/agentpack/analysis/ranking.py +0 -0
- {agentpack_cli-0.1.15 → agentpack_cli-0.1.19}/src/agentpack/analysis/rust_imports.py +0 -0
- {agentpack_cli-0.1.15 → agentpack_cli-0.1.19}/src/agentpack/analysis/symbols.py +0 -0
- {agentpack_cli-0.1.15 → agentpack_cli-0.1.19}/src/agentpack/analysis/tests.py +0 -0
- {agentpack_cli-0.1.15 → agentpack_cli-0.1.19}/src/agentpack/application/__init__.py +0 -0
- {agentpack_cli-0.1.15 → agentpack_cli-0.1.19}/src/agentpack/commands/__init__.py +0 -0
- {agentpack_cli-0.1.15 → agentpack_cli-0.1.19}/src/agentpack/commands/_shared.py +0 -0
- {agentpack_cli-0.1.15 → agentpack_cli-0.1.19}/src/agentpack/commands/benchmark.py +0 -0
- {agentpack_cli-0.1.15 → agentpack_cli-0.1.19}/src/agentpack/commands/claude_cmd.py +0 -0
- {agentpack_cli-0.1.15 → agentpack_cli-0.1.19}/src/agentpack/commands/diff.py +0 -0
- {agentpack_cli-0.1.15 → agentpack_cli-0.1.19}/src/agentpack/commands/explain.py +0 -0
- {agentpack_cli-0.1.15 → agentpack_cli-0.1.19}/src/agentpack/commands/init.py +0 -0
- {agentpack_cli-0.1.15 → agentpack_cli-0.1.19}/src/agentpack/commands/mcp_cmd.py +0 -0
- {agentpack_cli-0.1.15 → agentpack_cli-0.1.19}/src/agentpack/commands/monitor.py +0 -0
- {agentpack_cli-0.1.15 → agentpack_cli-0.1.19}/src/agentpack/commands/pack.py +0 -0
- {agentpack_cli-0.1.15 → agentpack_cli-0.1.19}/src/agentpack/commands/scan.py +0 -0
- {agentpack_cli-0.1.15 → agentpack_cli-0.1.19}/src/agentpack/commands/stats.py +0 -0
- {agentpack_cli-0.1.15 → agentpack_cli-0.1.19}/src/agentpack/commands/status.py +0 -0
- {agentpack_cli-0.1.15 → agentpack_cli-0.1.19}/src/agentpack/commands/summarize.py +0 -0
- {agentpack_cli-0.1.15 → agentpack_cli-0.1.19}/src/agentpack/commands/watch.py +0 -0
- {agentpack_cli-0.1.15 → agentpack_cli-0.1.19}/src/agentpack/core/__init__.py +0 -0
- {agentpack_cli-0.1.15 → agentpack_cli-0.1.19}/src/agentpack/core/bootstrap.py +0 -0
- {agentpack_cli-0.1.15 → agentpack_cli-0.1.19}/src/agentpack/core/cache.py +0 -0
- {agentpack_cli-0.1.15 → agentpack_cli-0.1.19}/src/agentpack/core/config.py +0 -0
- {agentpack_cli-0.1.15 → agentpack_cli-0.1.19}/src/agentpack/core/context_pack.py +0 -0
- {agentpack_cli-0.1.15 → agentpack_cli-0.1.19}/src/agentpack/core/diff.py +0 -0
- {agentpack_cli-0.1.15 → agentpack_cli-0.1.19}/src/agentpack/core/git.py +0 -0
- {agentpack_cli-0.1.15 → agentpack_cli-0.1.19}/src/agentpack/core/git_hooks.py +0 -0
- {agentpack_cli-0.1.15 → agentpack_cli-0.1.19}/src/agentpack/core/global_install.py +0 -0
- {agentpack_cli-0.1.15 → agentpack_cli-0.1.19}/src/agentpack/core/ignore.py +0 -0
- {agentpack_cli-0.1.15 → agentpack_cli-0.1.19}/src/agentpack/core/merkle.py +0 -0
- {agentpack_cli-0.1.15 → agentpack_cli-0.1.19}/src/agentpack/core/models.py +0 -0
- {agentpack_cli-0.1.15 → agentpack_cli-0.1.19}/src/agentpack/core/redactor.py +0 -0
- {agentpack_cli-0.1.15 → agentpack_cli-0.1.19}/src/agentpack/core/scanner.py +0 -0
- {agentpack_cli-0.1.15 → agentpack_cli-0.1.19}/src/agentpack/core/snapshot.py +0 -0
- {agentpack_cli-0.1.15 → agentpack_cli-0.1.19}/src/agentpack/core/token_estimator.py +0 -0
- {agentpack_cli-0.1.15 → agentpack_cli-0.1.19}/src/agentpack/core/vscode_tasks.py +0 -0
- {agentpack_cli-0.1.15 → agentpack_cli-0.1.19}/src/agentpack/data/agentpack.md +0 -0
- {agentpack_cli-0.1.15 → agentpack_cli-0.1.19}/src/agentpack/installers/__init__.py +0 -0
- {agentpack_cli-0.1.15 → agentpack_cli-0.1.19}/src/agentpack/installers/antigravity.py +0 -0
- {agentpack_cli-0.1.15 → agentpack_cli-0.1.19}/src/agentpack/installers/codex.py +0 -0
- {agentpack_cli-0.1.15 → agentpack_cli-0.1.19}/src/agentpack/installers/cursor.py +0 -0
- {agentpack_cli-0.1.15 → agentpack_cli-0.1.19}/src/agentpack/installers/windsurf.py +0 -0
- {agentpack_cli-0.1.15 → agentpack_cli-0.1.19}/src/agentpack/integrations/__init__.py +0 -0
- {agentpack_cli-0.1.15 → agentpack_cli-0.1.19}/src/agentpack/integrations/git_hooks.py +0 -0
- {agentpack_cli-0.1.15 → agentpack_cli-0.1.19}/src/agentpack/integrations/global_install.py +0 -0
- {agentpack_cli-0.1.15 → agentpack_cli-0.1.19}/src/agentpack/integrations/vscode_tasks.py +0 -0
- {agentpack_cli-0.1.15 → agentpack_cli-0.1.19}/src/agentpack/renderers/__init__.py +0 -0
- {agentpack_cli-0.1.15 → agentpack_cli-0.1.19}/src/agentpack/renderers/compact.py +0 -0
- {agentpack_cli-0.1.15 → agentpack_cli-0.1.19}/src/agentpack/renderers/markdown.py +0 -0
- {agentpack_cli-0.1.15 → agentpack_cli-0.1.19}/src/agentpack/renderers/receipts.py +0 -0
- {agentpack_cli-0.1.15 → agentpack_cli-0.1.19}/src/agentpack/session/__init__.py +0 -0
- {agentpack_cli-0.1.15 → agentpack_cli-0.1.19}/src/agentpack/session/state.py +0 -0
- {agentpack_cli-0.1.15 → agentpack_cli-0.1.19}/src/agentpack/summaries/__init__.py +0 -0
- {agentpack_cli-0.1.15 → agentpack_cli-0.1.19}/src/agentpack/summaries/base.py +0 -0
- {agentpack_cli-0.1.15 → agentpack_cli-0.1.19}/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.19
|
|
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.19).** 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`: runs `agentpack hook` — detects repo changes via `root_hash`, triggers background repack using your prompt as task. With MCP: emits Option-B hint (~100 tokens, task + top files). Without MCP: emits capped fallback (top 8 files, ≤3k chars)
|
|
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.19).** 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`: runs `agentpack hook` — detects repo changes via `root_hash`, triggers background repack using your prompt as task. With MCP: emits Option-B hint (~100 tokens, task + top files). Without MCP: emits capped fallback (top 8 files, ≤3k chars)
|
|
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
|
```
|
|
@@ -303,6 +303,9 @@ class PackService:
|
|
|
303
303
|
budget=plan.budget,
|
|
304
304
|
token_estimate=packed_tokens,
|
|
305
305
|
)
|
|
306
|
+
excluded_receipts = [r for r in plan.receipts if r.action == "excluded"]
|
|
307
|
+
# Budget-cut: files that scored OK but didn't fit — more useful signal than "score too low"
|
|
308
|
+
budget_cut = [r.path for r in plan.receipts if r.reason == "budget exhausted"][:10]
|
|
306
309
|
_record_metrics(
|
|
307
310
|
root,
|
|
308
311
|
task=request.task,
|
|
@@ -314,7 +317,10 @@ class PackService:
|
|
|
314
317
|
selected_count=len(plan.selected),
|
|
315
318
|
changed_count=len(plan.all_changed),
|
|
316
319
|
selected_paths=[sf.path for sf in plan.selected],
|
|
320
|
+
selected_hints=[{"path": sf.path, "why": sf.reasons[0] if sf.reasons else ""} for sf in plan.selected[:8]],
|
|
317
321
|
current_changed=plan.all_changed,
|
|
322
|
+
excluded_count=len(excluded_receipts),
|
|
323
|
+
excluded_paths=budget_cut,
|
|
318
324
|
)
|
|
319
325
|
|
|
320
326
|
return PackResult(
|
|
@@ -404,6 +410,9 @@ def _record_metrics(
|
|
|
404
410
|
changed_count: int,
|
|
405
411
|
selected_paths: list[str],
|
|
406
412
|
current_changed: set[str],
|
|
413
|
+
selected_hints: list[dict] | None = None,
|
|
414
|
+
excluded_count: int = 0,
|
|
415
|
+
excluded_paths: list[str] | None = None,
|
|
407
416
|
) -> None:
|
|
408
417
|
metrics_path = root / ".agentpack" / "metrics.jsonl"
|
|
409
418
|
accuracy = _compute_selection_accuracy(root, metrics_path, selected_paths, current_changed)
|
|
@@ -416,7 +425,10 @@ def _record_metrics(
|
|
|
416
425
|
"saving_pct": round(saving_pct, 1),
|
|
417
426
|
"selected_files": selected_count,
|
|
418
427
|
"changed_files": changed_count,
|
|
428
|
+
"excluded_files": excluded_count,
|
|
429
|
+
"excluded_paths": excluded_paths or [],
|
|
419
430
|
"selected_paths": selected_paths,
|
|
431
|
+
"selected_hints": selected_hints or [],
|
|
420
432
|
"phases": {k: round(v, 3) for k, v in phase_times.items()},
|
|
421
433
|
"total_s": round(sum(phase_times.values()), 3),
|
|
422
434
|
}
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
from __future__ import annotations
|
|
2
2
|
|
|
3
3
|
import typer
|
|
4
|
-
from agentpack.commands import init, scan, diff, status, stats, summarize, pack, install, monitor, explain, doctor, watch, claude_cmd, benchmark, mcp_cmd
|
|
4
|
+
from agentpack.commands import init, scan, diff, status, stats, summarize, pack, install, monitor, explain, doctor, watch, claude_cmd, benchmark, mcp_cmd, hook_cmd
|
|
5
5
|
from agentpack import __version__
|
|
6
6
|
|
|
7
7
|
|
|
@@ -21,7 +21,7 @@ def _main(
|
|
|
21
21
|
pass
|
|
22
22
|
|
|
23
23
|
|
|
24
|
-
for mod in [init, scan, diff, status, stats, summarize, pack, install, monitor, explain, doctor, watch, claude_cmd, benchmark, mcp_cmd]:
|
|
24
|
+
for mod in [init, scan, diff, status, stats, summarize, pack, install, monitor, explain, doctor, watch, claude_cmd, benchmark, mcp_cmd, hook_cmd]:
|
|
25
25
|
mod.register(app)
|
|
26
26
|
|
|
27
27
|
|
|
@@ -119,12 +119,37 @@ def register(app: typer.Typer) -> None:
|
|
|
119
119
|
import json as _json
|
|
120
120
|
_local_has_hooks = False
|
|
121
121
|
_global_has_hooks = False
|
|
122
|
+
|
|
123
|
+
def _has_stale_hooks(hooks: dict) -> bool:
|
|
124
|
+
"""Detect old inline-Python or context-injection hooks that should be upgraded."""
|
|
125
|
+
all_cmds = [
|
|
126
|
+
h.get("command", "")
|
|
127
|
+
for event_hooks in hooks.values()
|
|
128
|
+
for entry in event_hooks
|
|
129
|
+
for h in entry.get("hooks", [])
|
|
130
|
+
]
|
|
131
|
+
return any(
|
|
132
|
+
"context.claude.md" in cmd
|
|
133
|
+
or ".context_injected" in cmd
|
|
134
|
+
or (".mcp_reminded" in cmd and "python3" in cmd)
|
|
135
|
+
for cmd in all_cmds
|
|
136
|
+
)
|
|
137
|
+
|
|
138
|
+
def _has_current_hooks(hooks: dict) -> bool:
|
|
139
|
+
return "agentpack hook" in str(hooks)
|
|
140
|
+
|
|
122
141
|
if claude_settings.exists():
|
|
123
142
|
try:
|
|
124
143
|
data = _json.loads(claude_settings.read_text())
|
|
125
144
|
hooks = data.get("hooks", {})
|
|
126
145
|
if "UserPromptSubmit" in hooks or "SessionStart" in hooks:
|
|
127
|
-
|
|
146
|
+
if _has_stale_hooks(hooks):
|
|
147
|
+
console.print(" [yellow]![/] Claude hooks stale (local) — old injection hook detected. Run: agentpack install --agent claude")
|
|
148
|
+
ok = False
|
|
149
|
+
elif _has_current_hooks(hooks):
|
|
150
|
+
console.print(f" [green]✓[/] Claude hooks present (local): {claude_settings}")
|
|
151
|
+
else:
|
|
152
|
+
console.print(f" [green]✓[/] Claude hooks present (local): {claude_settings}")
|
|
128
153
|
_local_has_hooks = True
|
|
129
154
|
else:
|
|
130
155
|
console.print(" [yellow]![/] Claude hooks missing (local) — run: agentpack install --agent claude")
|
|
@@ -138,7 +163,10 @@ def register(app: typer.Typer) -> None:
|
|
|
138
163
|
data = _json.loads(global_claude_settings.read_text())
|
|
139
164
|
hooks = data.get("hooks", {})
|
|
140
165
|
if "UserPromptSubmit" in hooks or "SessionStart" in hooks:
|
|
141
|
-
|
|
166
|
+
if _has_stale_hooks(hooks):
|
|
167
|
+
console.print(" [yellow]![/] Claude hooks stale (global) — old injection hook detected. Run: agentpack install --agent claude --global")
|
|
168
|
+
else:
|
|
169
|
+
console.print(f" [green]✓[/] Claude hooks present (global): {global_claude_settings}")
|
|
142
170
|
_global_has_hooks = True
|
|
143
171
|
else:
|
|
144
172
|
console.print(" [yellow]![/] Claude hooks missing (global) — run: agentpack install --agent claude --global")
|
|
@@ -149,6 +177,35 @@ def register(app: typer.Typer) -> None:
|
|
|
149
177
|
if _local_has_hooks and not _global_has_hooks:
|
|
150
178
|
console.print(" [yellow]![/] Hooks local-only — context won't auto-inject in other repos. Run: agentpack install --agent claude --global")
|
|
151
179
|
|
|
180
|
+
# --- MCP server ---
|
|
181
|
+
console.print("\n[bold]MCP server[/]")
|
|
182
|
+
mcp_json = root / ".mcp.json"
|
|
183
|
+
global_claude_settings_for_mcp = Path.home() / ".claude" / "settings.json"
|
|
184
|
+
_local_has_mcp = False
|
|
185
|
+
_global_has_mcp = False
|
|
186
|
+
if mcp_json.exists():
|
|
187
|
+
try:
|
|
188
|
+
mcp_data = _json.loads(mcp_json.read_text())
|
|
189
|
+
if "agentpack" in mcp_data.get("mcpServers", {}):
|
|
190
|
+
console.print(f" [green]✓[/] MCP server registered (local): {mcp_json}")
|
|
191
|
+
_local_has_mcp = True
|
|
192
|
+
else:
|
|
193
|
+
console.print(" [yellow]![/] .mcp.json exists but agentpack missing — run: agentpack install --agent claude")
|
|
194
|
+
except Exception:
|
|
195
|
+
console.print(f" [yellow]![/] Could not parse {mcp_json}")
|
|
196
|
+
else:
|
|
197
|
+
console.print(" [dim]-[/] .mcp.json not present (run: agentpack install --agent claude)")
|
|
198
|
+
if global_claude_settings_for_mcp.exists():
|
|
199
|
+
try:
|
|
200
|
+
global_data = _json.loads(global_claude_settings_for_mcp.read_text())
|
|
201
|
+
if "agentpack" in global_data.get("mcpServers", {}):
|
|
202
|
+
console.print(f" [green]✓[/] MCP server registered (global): {global_claude_settings_for_mcp}")
|
|
203
|
+
_global_has_mcp = True
|
|
204
|
+
except Exception:
|
|
205
|
+
pass
|
|
206
|
+
if not _local_has_mcp and not _global_has_mcp:
|
|
207
|
+
console.print(" [yellow]![/] MCP server not registered — mcp__agentpack__* tools unavailable")
|
|
208
|
+
|
|
152
209
|
# --- Slash command ---
|
|
153
210
|
console.print("\n[bold]Slash command (/agentpack)[/]")
|
|
154
211
|
local_cmd = root / ".claude" / "commands" / "agentpack.md"
|
|
@@ -0,0 +1,223 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
import json
|
|
4
|
+
import subprocess
|
|
5
|
+
import sys
|
|
6
|
+
from pathlib import Path
|
|
7
|
+
|
|
8
|
+
import typer
|
|
9
|
+
|
|
10
|
+
from agentpack.commands._shared import _root
|
|
11
|
+
|
|
12
|
+
_TASK_FILE = ".agentpack/task.md"
|
|
13
|
+
_TASK_FILE_DEFAULT_MARKER = "Write or update the current coding task here."
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
def register(app: typer.Typer) -> None:
|
|
17
|
+
@app.command(name="hook")
|
|
18
|
+
def hook(
|
|
19
|
+
event: str = typer.Option("UserPromptSubmit", "--event", help="Hook event name."),
|
|
20
|
+
) -> None:
|
|
21
|
+
"""Run as a Claude Code hook. Reads stdin (JSON), emits additionalContext."""
|
|
22
|
+
root = _root()
|
|
23
|
+
if event == "UserPromptSubmit":
|
|
24
|
+
_run_user_prompt_submit(root)
|
|
25
|
+
elif event == "SessionStart":
|
|
26
|
+
_run_session_start(root)
|
|
27
|
+
else:
|
|
28
|
+
sys.exit(0)
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
# ---------------------------------------------------------------------------
|
|
32
|
+
# Public helpers (tested directly)
|
|
33
|
+
# ---------------------------------------------------------------------------
|
|
34
|
+
|
|
35
|
+
def _mcp_installed(root: Path) -> bool:
|
|
36
|
+
local_mcp = root / ".mcp.json"
|
|
37
|
+
if local_mcp.exists():
|
|
38
|
+
try:
|
|
39
|
+
cfg = json.loads(local_mcp.read_text())
|
|
40
|
+
if "agentpack" in cfg.get("mcpServers", {}):
|
|
41
|
+
return True
|
|
42
|
+
except Exception:
|
|
43
|
+
pass
|
|
44
|
+
global_settings = Path.home() / ".claude" / "settings.json"
|
|
45
|
+
if global_settings.exists():
|
|
46
|
+
try:
|
|
47
|
+
cfg = json.loads(global_settings.read_text())
|
|
48
|
+
if "agentpack" in cfg.get("mcpServers", {}):
|
|
49
|
+
return True
|
|
50
|
+
except Exception:
|
|
51
|
+
pass
|
|
52
|
+
return False
|
|
53
|
+
|
|
54
|
+
|
|
55
|
+
def _load_task_md(root: Path) -> str:
|
|
56
|
+
"""Return task.md content if user has written a real task (not the default placeholder)."""
|
|
57
|
+
task_path = root / _TASK_FILE
|
|
58
|
+
if not task_path.exists():
|
|
59
|
+
return ""
|
|
60
|
+
try:
|
|
61
|
+
content = task_path.read_text(encoding="utf-8").strip()
|
|
62
|
+
# Strip markdown heading
|
|
63
|
+
lines = [ln for ln in content.splitlines() if not ln.startswith("#")]
|
|
64
|
+
body = "\n".join(lines).strip()
|
|
65
|
+
if not body or _TASK_FILE_DEFAULT_MARKER in body:
|
|
66
|
+
return ""
|
|
67
|
+
return body[:200]
|
|
68
|
+
except Exception:
|
|
69
|
+
return ""
|
|
70
|
+
|
|
71
|
+
|
|
72
|
+
def _resolve_task(root: Path, prompt: str) -> str:
|
|
73
|
+
"""Merge task.md + prompt into best task description for repack."""
|
|
74
|
+
task_md = _load_task_md(root)
|
|
75
|
+
if task_md:
|
|
76
|
+
return task_md
|
|
77
|
+
return prompt[:200].strip() if prompt else "auto"
|
|
78
|
+
|
|
79
|
+
|
|
80
|
+
def _load_hints(root: Path, n: int = 5) -> list[dict]:
|
|
81
|
+
"""Return top-n selected_hints (path + why) from last metrics record."""
|
|
82
|
+
metrics_path = root / ".agentpack" / "metrics.jsonl"
|
|
83
|
+
if not metrics_path.exists():
|
|
84
|
+
return []
|
|
85
|
+
try:
|
|
86
|
+
lines = metrics_path.read_text(encoding="utf-8").splitlines()
|
|
87
|
+
for line in reversed(lines):
|
|
88
|
+
line = line.strip()
|
|
89
|
+
if not line:
|
|
90
|
+
continue
|
|
91
|
+
rec = json.loads(line)
|
|
92
|
+
hints = rec.get("selected_hints", [])
|
|
93
|
+
if hints:
|
|
94
|
+
return hints[:n]
|
|
95
|
+
# Fallback: old metrics without hints
|
|
96
|
+
paths = rec.get("selected_paths", [])
|
|
97
|
+
if paths:
|
|
98
|
+
return [{"path": p, "why": ""} for p in paths[:n]]
|
|
99
|
+
except Exception:
|
|
100
|
+
pass
|
|
101
|
+
return []
|
|
102
|
+
|
|
103
|
+
|
|
104
|
+
def _load_top_files(root: Path, n: int = 5) -> list[dict]:
|
|
105
|
+
"""Alias kept for backward compat with tests."""
|
|
106
|
+
return _load_hints(root, n)
|
|
107
|
+
|
|
108
|
+
|
|
109
|
+
def _load_pack_task(root: Path) -> str:
|
|
110
|
+
meta_path = root / ".agentpack" / "pack_metadata.json"
|
|
111
|
+
if not meta_path.exists():
|
|
112
|
+
return ""
|
|
113
|
+
try:
|
|
114
|
+
return json.loads(meta_path.read_text()).get("task", "")
|
|
115
|
+
except Exception:
|
|
116
|
+
return ""
|
|
117
|
+
|
|
118
|
+
|
|
119
|
+
def _current_root_hash(root: Path) -> str | None:
|
|
120
|
+
snap = root / ".agentpack" / "snapshots" / "latest.json"
|
|
121
|
+
if not snap.exists():
|
|
122
|
+
return None
|
|
123
|
+
try:
|
|
124
|
+
return json.loads(snap.read_text()).get("root_hash")
|
|
125
|
+
except Exception:
|
|
126
|
+
return None
|
|
127
|
+
|
|
128
|
+
|
|
129
|
+
# ---------------------------------------------------------------------------
|
|
130
|
+
# Event handlers
|
|
131
|
+
# ---------------------------------------------------------------------------
|
|
132
|
+
|
|
133
|
+
def _run_session_start(root: Path) -> None:
|
|
134
|
+
"""Clear sentinels so first prompt gets fresh context."""
|
|
135
|
+
for sentinel in [
|
|
136
|
+
root / ".agentpack" / ".mcp_reminded",
|
|
137
|
+
root / ".agentpack" / ".context_injected",
|
|
138
|
+
]:
|
|
139
|
+
try:
|
|
140
|
+
sentinel.unlink(missing_ok=True)
|
|
141
|
+
except Exception:
|
|
142
|
+
pass
|
|
143
|
+
# No output needed — SessionStart hooks don't inject additionalContext
|
|
144
|
+
|
|
145
|
+
|
|
146
|
+
def _run_user_prompt_submit(root: Path) -> None:
|
|
147
|
+
snap_sentinel = root / ".agentpack" / ".mcp_reminded"
|
|
148
|
+
|
|
149
|
+
try:
|
|
150
|
+
hook_data = json.loads(sys.stdin.read())
|
|
151
|
+
prompt = hook_data.get("prompt", "")
|
|
152
|
+
except Exception:
|
|
153
|
+
prompt = ""
|
|
154
|
+
|
|
155
|
+
task = _resolve_task(root, prompt)
|
|
156
|
+
|
|
157
|
+
current_hash = _current_root_hash(root)
|
|
158
|
+
reminded_hash = snap_sentinel.read_text().strip() if snap_sentinel.exists() else None
|
|
159
|
+
repo_changed = current_hash != reminded_hash
|
|
160
|
+
|
|
161
|
+
if repo_changed:
|
|
162
|
+
subprocess.Popen(
|
|
163
|
+
["agentpack", "pack", "--task", task, "--mode", "balanced", "--since", "HEAD~1"],
|
|
164
|
+
stdout=subprocess.DEVNULL,
|
|
165
|
+
stderr=subprocess.DEVNULL,
|
|
166
|
+
)
|
|
167
|
+
try:
|
|
168
|
+
snap_sentinel.write_text(current_hash or "1")
|
|
169
|
+
except Exception:
|
|
170
|
+
pass
|
|
171
|
+
|
|
172
|
+
has_mcp = _mcp_installed(root)
|
|
173
|
+
|
|
174
|
+
if has_mcp:
|
|
175
|
+
hints = _load_hints(root, n=5)
|
|
176
|
+
if hints:
|
|
177
|
+
files_lines = "\n".join(
|
|
178
|
+
f" - {h['path']}" + (f" — {h['why']}" if h.get("why") else "")
|
|
179
|
+
for h in hints
|
|
180
|
+
)
|
|
181
|
+
status_note = "(repacking — call pack_context for fresh results)" if repo_changed else "(index fresh)"
|
|
182
|
+
current_task = _load_task_md(root) or _load_pack_task(root) or "unknown"
|
|
183
|
+
msg = (
|
|
184
|
+
f"AgentPack {status_note}\n"
|
|
185
|
+
f"task: {current_task}\n"
|
|
186
|
+
f"top files:\n{files_lines}\n"
|
|
187
|
+
f"Call agentpack_pack_context(task=\"...\") for full ranked context."
|
|
188
|
+
)
|
|
189
|
+
else:
|
|
190
|
+
msg = (
|
|
191
|
+
"AgentPack active. No pack yet — call agentpack_pack_context(task=\"...\") "
|
|
192
|
+
"to build context for this task."
|
|
193
|
+
)
|
|
194
|
+
else:
|
|
195
|
+
hints = _load_hints(root, n=8)
|
|
196
|
+
current_task = _load_task_md(root) or _load_pack_task(root) or "unknown"
|
|
197
|
+
if hints:
|
|
198
|
+
files_lines = "\n".join(
|
|
199
|
+
f" - {h['path']}" + (f" — {h['why']}" if h.get("why") else "")
|
|
200
|
+
for h in hints
|
|
201
|
+
)
|
|
202
|
+
changed_note = " (repacking in background)" if repo_changed else ""
|
|
203
|
+
msg = (
|
|
204
|
+
f"AgentPack context{changed_note}\n"
|
|
205
|
+
f"task: {current_task}\n"
|
|
206
|
+
f"top files:\n{files_lines}\n\n"
|
|
207
|
+
f"For richer context, install MCP: agentpack install --agent claude"
|
|
208
|
+
)
|
|
209
|
+
else:
|
|
210
|
+
msg = (
|
|
211
|
+
"AgentPack active. Run `agentpack pack --task \"<task>\"` to build context.\n"
|
|
212
|
+
"For auto context, install MCP: agentpack install --agent claude"
|
|
213
|
+
)
|
|
214
|
+
|
|
215
|
+
if len(msg) > 3000:
|
|
216
|
+
msg = msg[:2970] + "\n... [truncated]"
|
|
217
|
+
|
|
218
|
+
print(json.dumps({
|
|
219
|
+
"hookSpecificOutput": {
|
|
220
|
+
"hookEventName": "UserPromptSubmit",
|
|
221
|
+
"additionalContext": msg,
|
|
222
|
+
}
|
|
223
|
+
}))
|
|
@@ -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
|
|
|
@@ -164,11 +168,14 @@ def register(app: typer.Typer) -> None:
|
|
|
164
168
|
# --- Agent-specific config ---
|
|
165
169
|
if agent == "claude":
|
|
166
170
|
if not dry_run:
|
|
167
|
-
|
|
171
|
+
inst = ClaudeInstaller()
|
|
172
|
+
hook_action = inst.patch_claude_settings(root, global_install=True)
|
|
168
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}.[/]")
|
|
169
176
|
_install_slash_command(root, global_install=True)
|
|
170
177
|
else:
|
|
171
|
-
console.print("\n[dim]Would patch: ~/.claude/settings.json (hooks)[/]")
|
|
178
|
+
console.print("\n[dim]Would patch: ~/.claude/settings.json (hooks + mcpServers for global)[/]")
|
|
172
179
|
console.print("[dim]Would install: ~/.claude/commands/agentpack.md (slash command)[/]")
|
|
173
180
|
|
|
174
181
|
elif agent == "cursor":
|