agentpack-cli 0.1.0__py3-none-any.whl

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 (80) hide show
  1. agentpack/__init__.py +3 -0
  2. agentpack/adapters/__init__.py +0 -0
  3. agentpack/adapters/base.py +22 -0
  4. agentpack/adapters/claude.py +32 -0
  5. agentpack/adapters/codex.py +26 -0
  6. agentpack/adapters/cursor.py +29 -0
  7. agentpack/adapters/generic.py +18 -0
  8. agentpack/adapters/windsurf.py +26 -0
  9. agentpack/analysis/__init__.py +0 -0
  10. agentpack/analysis/dependency_graph.py +80 -0
  11. agentpack/analysis/go_imports.py +32 -0
  12. agentpack/analysis/java_imports.py +19 -0
  13. agentpack/analysis/js_ts_imports.py +53 -0
  14. agentpack/analysis/python_imports.py +45 -0
  15. agentpack/analysis/ranking.py +400 -0
  16. agentpack/analysis/rust_imports.py +32 -0
  17. agentpack/analysis/symbols.py +154 -0
  18. agentpack/analysis/tests.py +30 -0
  19. agentpack/application/__init__.py +0 -0
  20. agentpack/application/pack_service.py +352 -0
  21. agentpack/cli.py +33 -0
  22. agentpack/commands/__init__.py +0 -0
  23. agentpack/commands/_shared.py +13 -0
  24. agentpack/commands/benchmark.py +302 -0
  25. agentpack/commands/claude_cmd.py +55 -0
  26. agentpack/commands/diff.py +46 -0
  27. agentpack/commands/doctor.py +185 -0
  28. agentpack/commands/explain.py +238 -0
  29. agentpack/commands/init.py +79 -0
  30. agentpack/commands/install.py +252 -0
  31. agentpack/commands/monitor.py +105 -0
  32. agentpack/commands/pack.py +188 -0
  33. agentpack/commands/scan.py +51 -0
  34. agentpack/commands/session.py +204 -0
  35. agentpack/commands/stats.py +138 -0
  36. agentpack/commands/status.py +37 -0
  37. agentpack/commands/summarize.py +64 -0
  38. agentpack/commands/watch.py +185 -0
  39. agentpack/core/__init__.py +0 -0
  40. agentpack/core/bootstrap.py +46 -0
  41. agentpack/core/cache.py +41 -0
  42. agentpack/core/config.py +101 -0
  43. agentpack/core/context_pack.py +222 -0
  44. agentpack/core/diff.py +40 -0
  45. agentpack/core/git.py +145 -0
  46. agentpack/core/git_hooks.py +8 -0
  47. agentpack/core/global_install.py +14 -0
  48. agentpack/core/ignore.py +66 -0
  49. agentpack/core/merkle.py +8 -0
  50. agentpack/core/models.py +115 -0
  51. agentpack/core/redactor.py +99 -0
  52. agentpack/core/scanner.py +150 -0
  53. agentpack/core/snapshot.py +60 -0
  54. agentpack/core/token_estimator.py +26 -0
  55. agentpack/core/vscode_tasks.py +5 -0
  56. agentpack/data/agentpack.md +160 -0
  57. agentpack/installers/__init__.py +0 -0
  58. agentpack/installers/claude.py +160 -0
  59. agentpack/installers/codex.py +54 -0
  60. agentpack/installers/cursor.py +76 -0
  61. agentpack/installers/windsurf.py +50 -0
  62. agentpack/integrations/__init__.py +0 -0
  63. agentpack/integrations/git_hooks.py +109 -0
  64. agentpack/integrations/global_install.py +221 -0
  65. agentpack/integrations/vscode_tasks.py +85 -0
  66. agentpack/renderers/__init__.py +3 -0
  67. agentpack/renderers/compact.py +75 -0
  68. agentpack/renderers/markdown.py +144 -0
  69. agentpack/renderers/receipts.py +10 -0
  70. agentpack/session/__init__.py +33 -0
  71. agentpack/session/state.py +105 -0
  72. agentpack/summaries/__init__.py +0 -0
  73. agentpack/summaries/base.py +42 -0
  74. agentpack/summaries/llm.py +100 -0
  75. agentpack/summaries/offline.py +97 -0
  76. agentpack_cli-0.1.0.dist-info/METADATA +1391 -0
  77. agentpack_cli-0.1.0.dist-info/RECORD +80 -0
  78. agentpack_cli-0.1.0.dist-info/WHEEL +4 -0
  79. agentpack_cli-0.1.0.dist-info/entry_points.txt +2 -0
  80. agentpack_cli-0.1.0.dist-info/licenses/LICENSE +21 -0
@@ -0,0 +1,115 @@
1
+ from pathlib import Path
2
+ from typing import Literal
3
+ from pydantic import BaseModel
4
+
5
+
6
+ class ScanResult(BaseModel):
7
+ packable: list["FileInfo"]
8
+ ignored: list["FileInfo"]
9
+ binary: list["FileInfo"]
10
+
11
+ @property
12
+ def all_files(self) -> list["FileInfo"]:
13
+ return self.packable + self.ignored + self.binary
14
+
15
+
16
+ class FileInfo(BaseModel):
17
+ path: str
18
+ abs_path: Path
19
+ language: str | None = None
20
+ size_bytes: int
21
+ estimated_tokens: int
22
+ hash: str | None = None
23
+ ignored: bool = False
24
+ binary: bool = False
25
+ too_large: bool = False
26
+ content: str | None = None # cached at scan time; avoids re-reads in scoring/selection
27
+
28
+ model_config = {"arbitrary_types_allowed": True}
29
+
30
+
31
+ class Symbol(BaseModel):
32
+ name: str
33
+ kind: Literal["class", "function", "method", "variable"]
34
+ start_line: int
35
+ end_line: int
36
+ signature: str | None = None
37
+ summary: str | None = None
38
+ body: str | None = None # source text captured at extraction time; no re-read needed
39
+
40
+
41
+ class FileSummary(BaseModel):
42
+ path: str
43
+ hash: str
44
+ language: str | None = None
45
+ provider: str = "offline"
46
+ schema_version: int = 1
47
+ summary: str
48
+ imports: list[str] = []
49
+ symbols: list[Symbol] = []
50
+
51
+
52
+ class SelectedFile(BaseModel):
53
+ path: str
54
+ language: str | None = None
55
+ score: float
56
+ include_mode: Literal["full", "symbols", "summary"]
57
+ reasons: list[str]
58
+ content: str | None = None
59
+ summary: str | None = None
60
+ symbols: list[Symbol] = []
61
+ redaction_warnings: list[str] = []
62
+
63
+
64
+ class Receipt(BaseModel):
65
+ path: str
66
+ action: Literal["included", "excluded", "summarized"]
67
+ reason: str
68
+
69
+
70
+ class ContextPack(BaseModel):
71
+ task: str
72
+ agent: str
73
+ mode: Literal["minimal", "balanced", "deep"]
74
+ budget: int
75
+ token_estimate: int
76
+ raw_repo_tokens: int
77
+ after_ignore_tokens: int
78
+ estimated_savings_percent: float
79
+ changed_files: list[str]
80
+ selected_files: list[SelectedFile]
81
+ receipts: list[Receipt]
82
+ redaction_warnings: list[str] = []
83
+ stale: bool = False
84
+
85
+
86
+ class DependencyNode(BaseModel):
87
+ path: str
88
+ imports: list[str] = []
89
+ imported_by: list[str] = []
90
+ tests: list[str] = []
91
+
92
+
93
+ class DependencyGraph(BaseModel):
94
+ nodes: dict[str, DependencyNode] = {}
95
+
96
+ def get(self, path: str) -> DependencyNode:
97
+ return self.nodes.get(path, DependencyNode(path=path))
98
+
99
+ def __getitem__(self, path: str) -> DependencyNode:
100
+ return self.nodes[path]
101
+
102
+ def __setitem__(self, path: str, node: DependencyNode) -> None:
103
+ self.nodes[path] = node
104
+
105
+ def __contains__(self, path: object) -> bool:
106
+ return path in self.nodes
107
+
108
+ def __iter__(self): # type: ignore[override]
109
+ return iter(self.nodes)
110
+
111
+ def __len__(self) -> int:
112
+ return len(self.nodes)
113
+
114
+ def items(self): # type: ignore[override]
115
+ return self.nodes.items()
@@ -0,0 +1,99 @@
1
+ from __future__ import annotations
2
+
3
+ import re
4
+
5
+ # Placeholder patterns — values matching these are NOT redacted
6
+ _PLACEHOLDER_RE = re.compile(
7
+ r"your[_-]?(?:api[_-]?)?(?:key|token|secret)[_-]?here|"
8
+ r"<[A-Z_]{3,}(?:KEY|TOKEN|SECRET|PASSWORD)[A-Z_]*>|"
9
+ r"xxx+|"
10
+ r"insert[_-]?(?:key|token|secret)[_-]?here|"
11
+ r"changeme|"
12
+ r"example[_-]?(?:key|token|secret)|"
13
+ r"(?:key|token|secret)[_-]?example",
14
+ re.IGNORECASE,
15
+ )
16
+
17
+ # (pattern, label, value_group): when value_group is set, only that group is redacted.
18
+ _SECRET_PATTERNS: list[tuple[re.Pattern[str], str, int | None]] = [
19
+ (re.compile(
20
+ r"-----BEGIN (?:RSA |EC |OPENSSH )?PRIVATE KEY-----[\s\S]*?"
21
+ r"-----END (?:RSA |EC |OPENSSH )?PRIVATE KEY-----"
22
+ ), "private-key", None),
23
+ (re.compile(
24
+ r"eyJ[A-Za-z0-9\-_]{20,}\.[A-Za-z0-9\-_]{20,}\.[A-Za-z0-9\-_]{20,}"
25
+ ), "jwt", None),
26
+ (re.compile(r"AKIA[0-9A-Z]{16}"), "aws-access-key", None),
27
+ (re.compile(
28
+ r"(?i)(aws_secret(?:_access_key)?\s*[=:\"'\s]+\s*)([A-Za-z0-9+/]{40})"
29
+ ), "aws-secret-key", 2),
30
+ (re.compile(r"gh[pousr]_[A-Za-z0-9]{36,}"), "github-token", None),
31
+ # Anthropic before OpenAI to avoid partial match on sk- prefix
32
+ (re.compile(r"sk-ant-[A-Za-z0-9\-]{32,}"), "anthropic-key", None),
33
+ (re.compile(r"sk-[A-Za-z0-9]{32,}"), "openai-key", None),
34
+ # Generic: handles key=value, key='value', key="value", key: value
35
+ (re.compile(
36
+ r"(?i)(?:api[_-]?key|token|secret|password|passwd|auth[_-]?key)"
37
+ r"\s*[=:]\s*[\"']?\s*([A-Za-z0-9+/\-_]{40,})"
38
+ ), "api-key", 1),
39
+ ]
40
+
41
+
42
+ def _is_placeholder(value: str) -> bool:
43
+ return bool(_PLACEHOLDER_RE.search(value))
44
+
45
+
46
+ def _line_of(text: str, start: int) -> int:
47
+ """Return 1-indexed line number for character offset *start* in *text*."""
48
+ return text.count("\n", 0, start) + 1
49
+
50
+
51
+ def redact_secrets(text: str, path: str) -> tuple[str, list[str]]:
52
+ """Scan *text* for secrets and replace each with ``[REDACTED:<type>]``.
53
+
54
+ Returns ``(redacted_text, warnings)`` where each warning is a
55
+ human-readable string like ``"src/config.py: aws-access-key detected (line 12)"``.
56
+ """
57
+ warnings: list[str] = []
58
+ # Collect (start, end, replacement_str) tuples; apply in reverse order.
59
+ replacements: list[tuple[int, int, str]] = []
60
+ # Track redacted spans to avoid double-reporting overlapping matches.
61
+ redacted_spans: list[tuple[int, int]] = []
62
+
63
+ def _overlaps(start: int, end: int) -> bool:
64
+ for rs, re_ in redacted_spans:
65
+ if start < re_ and end > rs:
66
+ return True
67
+ return False
68
+
69
+ for pattern, label, value_group in _SECRET_PATTERNS:
70
+ for m in pattern.finditer(text):
71
+ if value_group is not None:
72
+ secret_val = m.group(value_group)
73
+ secret_start = m.start(value_group)
74
+ secret_end = m.end(value_group)
75
+ else:
76
+ secret_val = m.group(0)
77
+ secret_start = m.start()
78
+ secret_end = m.end()
79
+
80
+ if _is_placeholder(secret_val):
81
+ continue
82
+ if _overlaps(secret_start, secret_end):
83
+ continue
84
+
85
+ line_no = _line_of(text, secret_start)
86
+ replacements.append((secret_start, secret_end, f"[REDACTED:{label}]"))
87
+ redacted_spans.append((secret_start, secret_end))
88
+ warnings.append(f"{path}: {label} detected (line {line_no})")
89
+
90
+ if not replacements:
91
+ return text, warnings
92
+
93
+ # Apply replacements in reverse order to preserve earlier offsets
94
+ replacements.sort(key=lambda r: r[0], reverse=True)
95
+ chars = list(text)
96
+ for start, end, repl in replacements:
97
+ chars[start:end] = list(repl)
98
+
99
+ return "".join(chars), warnings
@@ -0,0 +1,150 @@
1
+ from __future__ import annotations
2
+
3
+ import hashlib
4
+ from pathlib import Path
5
+
6
+ import pathspec
7
+
8
+ from agentpack.core.ignore import load_spec, is_ignored
9
+ from agentpack.core.models import FileInfo, ScanResult
10
+ from agentpack.core.token_estimator import estimate_tokens
11
+
12
+ BINARY_EXTENSIONS = {
13
+ ".png", ".jpg", ".jpeg", ".gif", ".bmp", ".ico", ".svg",
14
+ ".pdf", ".zip", ".tar", ".gz", ".bz2", ".7z", ".rar",
15
+ ".exe", ".dll", ".so", ".dylib", ".bin",
16
+ ".mp3", ".mp4", ".wav", ".avi", ".mov",
17
+ ".ttf", ".woff", ".woff2", ".eot",
18
+ ".pyc", ".pyo", ".class",
19
+ ".db", ".sqlite", ".sqlite3",
20
+ }
21
+
22
+ LANGUAGE_MAP: dict[str, str] = {
23
+ ".py": "python",
24
+ ".js": "javascript",
25
+ ".jsx": "javascript",
26
+ ".ts": "typescript",
27
+ ".tsx": "typescript",
28
+ ".mjs": "javascript",
29
+ ".cjs": "javascript",
30
+ ".go": "go",
31
+ ".rs": "rust",
32
+ ".java": "java",
33
+ ".kt": "kotlin",
34
+ ".rb": "ruby",
35
+ ".php": "php",
36
+ ".c": "c",
37
+ ".cpp": "cpp",
38
+ ".h": "c",
39
+ ".hpp": "cpp",
40
+ ".cs": "csharp",
41
+ ".html": "html",
42
+ ".css": "css",
43
+ ".scss": "scss",
44
+ ".json": "json",
45
+ ".yaml": "yaml",
46
+ ".yml": "yaml",
47
+ ".toml": "toml",
48
+ ".md": "markdown",
49
+ ".sh": "bash",
50
+ ".bash": "bash",
51
+ ".zsh": "bash",
52
+ ".sql": "sql",
53
+ ".tf": "terraform",
54
+ ".xml": "xml",
55
+ }
56
+
57
+ ALWAYS_SKIP = {".git", ".agentpack"}
58
+
59
+
60
+ def file_hash(path: Path) -> str:
61
+ h = hashlib.sha256()
62
+ with path.open("rb") as f:
63
+ for chunk in iter(lambda: f.read(8192), b""):
64
+ h.update(chunk)
65
+ return h.hexdigest()
66
+
67
+
68
+ def _is_binary(path: Path) -> bool:
69
+ if path.suffix.lower() in BINARY_EXTENSIONS:
70
+ return True
71
+ try:
72
+ chunk = path.read_bytes()[:1024]
73
+ return b"\x00" in chunk
74
+ except OSError:
75
+ return True
76
+
77
+
78
+ def scan(
79
+ root: Path,
80
+ ignore_spec: pathspec.PathSpec,
81
+ max_file_tokens: int = 4000,
82
+ ) -> ScanResult:
83
+ packable: list[FileInfo] = []
84
+ ignored: list[FileInfo] = []
85
+ binary: list[FileInfo] = []
86
+
87
+ for abs_path in root.rglob("*"):
88
+ if not abs_path.is_file():
89
+ continue
90
+
91
+ rel = abs_path.relative_to(root)
92
+ parts = rel.parts
93
+
94
+ if any(p in ALWAYS_SKIP for p in parts):
95
+ continue
96
+
97
+ rel_str = str(rel)
98
+
99
+ if is_ignored(ignore_spec, rel_str):
100
+ ignored.append(
101
+ FileInfo(
102
+ path=rel_str,
103
+ abs_path=abs_path,
104
+ size_bytes=abs_path.stat().st_size,
105
+ estimated_tokens=0,
106
+ ignored=True,
107
+ )
108
+ )
109
+ continue
110
+
111
+ if _is_binary(abs_path):
112
+ size = abs_path.stat().st_size
113
+ lang = LANGUAGE_MAP.get(abs_path.suffix.lower())
114
+ binary.append(
115
+ FileInfo(
116
+ path=rel_str,
117
+ abs_path=abs_path,
118
+ language=lang,
119
+ size_bytes=size,
120
+ estimated_tokens=0,
121
+ binary=True,
122
+ )
123
+ )
124
+ continue
125
+
126
+ size = abs_path.stat().st_size
127
+ lang = LANGUAGE_MAP.get(abs_path.suffix.lower())
128
+
129
+ try:
130
+ text = abs_path.read_text(errors="replace")
131
+ except OSError:
132
+ continue
133
+
134
+ tokens = estimate_tokens(text)
135
+ too_large = tokens > max_file_tokens
136
+
137
+ packable.append(
138
+ FileInfo(
139
+ path=rel_str,
140
+ abs_path=abs_path,
141
+ language=lang,
142
+ size_bytes=size,
143
+ estimated_tokens=tokens,
144
+ hash=file_hash(abs_path),
145
+ too_large=too_large,
146
+ content=text,
147
+ )
148
+ )
149
+
150
+ return ScanResult(packable=packable, ignored=ignored, binary=binary)
@@ -0,0 +1,60 @@
1
+ from __future__ import annotations
2
+
3
+ import json
4
+ from datetime import datetime, timezone
5
+ from pathlib import Path
6
+ from typing import Any
7
+
8
+ from agentpack.core.merkle import root_hash
9
+ from agentpack.core.models import FileInfo
10
+
11
+
12
+ SNAPSHOT_VERSION = 1
13
+
14
+
15
+ def _snapshots_dir(root: Path) -> Path:
16
+ return root / ".agentpack" / "snapshots"
17
+
18
+
19
+ def _latest_path(root: Path) -> Path:
20
+ return _snapshots_dir(root) / "latest.json"
21
+
22
+
23
+ def build_snapshot(files: list[FileInfo]) -> dict[str, Any]:
24
+ """Build a snapshot from packable FileInfo objects. Skips ignored and binary entries defensively."""
25
+ file_data: dict[str, Any] = {}
26
+ hashes: dict[str, str] = {}
27
+ for f in files:
28
+ if f.ignored or f.binary:
29
+ continue
30
+ file_data[f.path] = {
31
+ "hash": f.hash,
32
+ "size_bytes": f.size_bytes,
33
+ "estimated_tokens": f.estimated_tokens,
34
+ "language": f.language,
35
+ }
36
+ if f.hash:
37
+ hashes[f.path] = f.hash
38
+
39
+ return {
40
+ "version": SNAPSHOT_VERSION,
41
+ "root_hash": root_hash(hashes),
42
+ "created_at": datetime.now(timezone.utc).isoformat(),
43
+ "files": file_data,
44
+ }
45
+
46
+
47
+ def save_snapshot(snapshot: dict[str, Any], root: Path) -> None:
48
+ snapshots_dir = _snapshots_dir(root)
49
+ snapshots_dir.mkdir(parents=True, exist_ok=True)
50
+ _latest_path(root).write_text(json.dumps(snapshot, indent=2))
51
+
52
+
53
+ def load_snapshot(root: Path) -> dict[str, Any] | None:
54
+ path = _latest_path(root)
55
+ if not path.exists():
56
+ return None
57
+ try:
58
+ return json.loads(path.read_text())
59
+ except (json.JSONDecodeError, OSError):
60
+ return None
@@ -0,0 +1,26 @@
1
+ from __future__ import annotations
2
+
3
+ _encoder = None
4
+
5
+
6
+ def _get_encoder():
7
+ global _encoder
8
+ if _encoder is None:
9
+ try:
10
+ import tiktoken
11
+ _encoder = tiktoken.get_encoding("cl100k_base")
12
+ except ImportError:
13
+ _encoder = False
14
+ return _encoder
15
+
16
+
17
+ def estimate_tokens(text: str) -> int:
18
+ enc = _get_encoder()
19
+ if enc:
20
+ return max(1, len(enc.encode(text, disallowed_special=())))
21
+ return max(1, len(text) // 4)
22
+
23
+
24
+ def estimate_tokens_bytes(size_bytes: int) -> int:
25
+ # byte-level fallback when text is unavailable
26
+ return max(1, size_bytes // 4)
@@ -0,0 +1,5 @@
1
+ """Backward-compat shim — moved to agentpack.integrations.vscode_tasks."""
2
+ from agentpack.integrations.vscode_tasks import ( # noqa: F401
3
+ install_vscode_tasks,
4
+ remove_vscode_tasks,
5
+ )
@@ -0,0 +1,160 @@
1
+ ---
2
+ description: Pack repo context and immediately start working on the task. Supports session mode (start once, work normally) and manual pack mode. Reads context and begins helping — no manual piping needed.
3
+ ---
4
+
5
+ # AgentPack
6
+
7
+ Pack repo context and immediately start working on the task.
8
+
9
+ ## Usage
10
+
11
+ ```
12
+ /agentpack --task "fix Redis SSE cancellation issue"
13
+ /agentpack --task "add rate limiting to auth endpoints" --mode deep
14
+ /agentpack --task auto # infer task from branch + changed files + git log
15
+ /agentpack init # interactive: prompts for default mode
16
+ /agentpack status
17
+ /agentpack stats
18
+ /agentpack diff
19
+ /agentpack summarize
20
+ /agentpack install
21
+ /agentpack session start
22
+ /agentpack session status
23
+ /agentpack session refresh --task "new task"
24
+ /agentpack session stop
25
+ /agentpack watch
26
+ /agentpack claude
27
+ /agentpack explain --task auto
28
+ /agentpack explain --file src/auth/session.py
29
+ /agentpack explain --omitted
30
+ ```
31
+
32
+ ## Session Mode (recommended)
33
+
34
+ If a session is already running (`.agentpack/session.json` exists and `"active": true`):
35
+
36
+ 1. Read `.agentpack/context.md` — context is already fresh.
37
+ 2. If the user gives a new coding task, write a one-line summary to `.agentpack/task.md`.
38
+ 3. Re-read `.agentpack/context.md` after watch mode refreshes it (a few seconds).
39
+ 4. Proceed with the task using the context you just read.
40
+
41
+ To start a session:
42
+
43
+ ```bash
44
+ agentpack session start # creates session + generates initial context
45
+ agentpack session start --agent claude # specify agent
46
+ agentpack session start --task "fix bug" # set initial task
47
+
48
+ agentpack watch # in another terminal — auto-refreshes on changes
49
+ ```
50
+
51
+ To check session state:
52
+
53
+ ```bash
54
+ agentpack session status # shows active, agent, mode, last refresh, refresh count
55
+ agentpack stats # shows session panel + token stats + top files
56
+ ```
57
+
58
+ To force a refresh:
59
+
60
+ ```bash
61
+ agentpack session refresh
62
+ agentpack session refresh --task "new task description"
63
+ ```
64
+
65
+ To stop:
66
+
67
+ ```bash
68
+ agentpack session stop
69
+ ```
70
+
71
+ Then use normal prompts — context stays current while `watch` is running.
72
+
73
+ ## Manual Pack Mode (no session)
74
+
75
+ ```bash
76
+ agentpack pack --agent claude --task "<task>" --mode balanced
77
+ ```
78
+
79
+ Then read `.agentpack/context.claude.md` in full.
80
+
81
+ ## Process
82
+
83
+ ### Step 1: Check agentpack is installed
84
+
85
+ ```bash
86
+ agentpack --help 2>/dev/null || pip install agentpack-cli
87
+ ```
88
+
89
+ ### Step 2: Initialize if not already done
90
+
91
+ ```bash
92
+ test -f .agentpack/config.toml || agentpack init --yes
93
+ ```
94
+
95
+ ### Step 3: Determine workflow
96
+
97
+ **Session active** (`.agentpack/session.json` exists, `"active": true`):
98
+ - Read `.agentpack/context.md`
99
+ - Update `.agentpack/task.md` if task changed
100
+ - Proceed immediately
101
+
102
+ **No session**:
103
+ - Run `agentpack session start` or `agentpack pack --task auto`
104
+ - Read the context file
105
+ - Proceed
106
+
107
+ ### Step 4: Immediately start working
108
+
109
+ Using the context you just read:
110
+
111
+ 1. **Orient** — state which files are changed and what the key code areas are (2-3 sentences)
112
+ 2. **Diagnose or plan** — root cause for bugs, approach for features. Reference specific file:line
113
+ 3. **Start working** — edit code, fix the issue, implement the feature
114
+
115
+ Do not say "context pack ready" and stop. Do not tell the user to run more commands.
116
+
117
+ ## Stale pack handling
118
+
119
+ If `agentpack status` exits non-zero or context seems unrelated to the task:
120
+ - Run `agentpack session refresh` (if session active)
121
+ - Or run `agentpack pack --task auto` (manual mode)
122
+ - Re-read the context, then proceed
123
+
124
+ Do not ask the user — just refresh and proceed.
125
+
126
+ ## Debugging selection
127
+
128
+ ```bash
129
+ agentpack explain --task auto # show ranked file list
130
+ agentpack explain --file src/auth/session.py # per-file score breakdown
131
+ agentpack explain --omitted # see what was excluded and why
132
+ ```
133
+
134
+ ## Subcommand routing
135
+
136
+ | User types | Action |
137
+ |---|---|
138
+ | `/agentpack --task "..."` | check session or pack + read + work |
139
+ | `/agentpack` | check session or pack with `--task auto` + read + work |
140
+ | `/agentpack session start` | `agentpack session start` |
141
+ | `/agentpack session status` | `agentpack session status` |
142
+ | `/agentpack session refresh` | `agentpack session refresh` |
143
+ | `/agentpack session stop` | `agentpack session stop` |
144
+ | `/agentpack watch` | `agentpack watch` (foreground, Ctrl+C to stop) |
145
+ | `/agentpack claude` | `agentpack claude` (refresh + launch claude) |
146
+ | `/agentpack init` | `agentpack init` only |
147
+ | `/agentpack status` | check staleness |
148
+ | `/agentpack stats` | session info + token savings |
149
+ | `/agentpack diff` | changed files |
150
+ | `/agentpack summarize` | rebuild offline summary cache |
151
+ | `/agentpack install` | `agentpack install --agent claude` |
152
+ | `/agentpack explain` | show ranked file selection |
153
+
154
+ ## Notes
155
+
156
+ - All commands are local — no API calls
157
+ - `--task auto` infers from branch name → changed file paths → recent commit
158
+ - Changed files are highest priority in context
159
+ - Session context files: `.agentpack/context.md` (readable), `.agentpack/context.compact.md` (compact)
160
+ - Never overwrite `.agentignore` or `config.toml` without `--force`
File without changes