@inneranimalmedia/agentsam-sdk 1.7.0 → 1.9.0

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 (36) hide show
  1. package/DEVELOPMENT.md +25 -5
  2. package/README.md +2 -0
  3. package/docs/RELEASES.iam-mirror.md +20 -0
  4. package/docs/RELEASES.md +9 -0
  5. package/package.json +9 -4
  6. package/protocol/README.md +51 -0
  7. package/protocol/dual-repo-sync.md +35 -0
  8. package/python/README.md +12 -0
  9. package/python/agentsam_sdk/__init__.py +9 -0
  10. package/python/agentsam_sdk/cli.py +262 -0
  11. package/python/agentsam_sdk/data/__init__.py +0 -0
  12. package/python/agentsam_sdk/data/agentsam_walk.py +157 -0
  13. package/python/agentsam_sdk/data/d1_adapter.py +124 -0
  14. package/python/agentsam_sdk/data/d1_bloat.py +445 -0
  15. package/python/agentsam_sdk/repository/__init__.py +26 -0
  16. package/python/agentsam_sdk/repository/__main__.py +3 -0
  17. package/python/agentsam_sdk/repository/inspect.py +496 -0
  18. package/python/agentsam_sdk/repository/inventory.py +351 -0
  19. package/python/agentsam_sdk/repository/scan_bloat.py +173 -0
  20. package/python/agentsam_sdk/runtime/__init__.py +0 -0
  21. package/python/agentsam_sdk/runtime/contract.py +105 -0
  22. package/python/docs/gaps.md +63 -0
  23. package/python/docs/tooling.md +67 -0
  24. package/python/protocol/README.md +51 -0
  25. package/python/protocol/dual-repo-sync.md +35 -0
  26. package/python/pyproject.toml +16 -0
  27. package/python/scripts/check-host-tooling.sh +65 -0
  28. package/python/tests/__init__.py +0 -0
  29. package/python/tests/fixtures/sample_tables.json +17 -0
  30. package/python/tests/fixtures.py +95 -0
  31. package/python/tests/test_agentsam_walk.py +31 -0
  32. package/python/tests/test_contract.py +32 -0
  33. package/python/tests/test_d1_bloat.py +93 -0
  34. package/python/tests/test_repository_inspect.py +84 -0
  35. package/python/tests/test_repository_inventory.py +53 -0
  36. package/python/tests/test_scan_bloat.py +31 -0
package/DEVELOPMENT.md CHANGED
@@ -1,4 +1,4 @@
1
- # Developing agentsam-sdk with Inner Animal Media
1
+ # DEVELOPING AGENTSAM-SDK WITH INNER ANIMAL MEDIA
2
2
 
3
3
  ## Local smoke test
4
4
 
@@ -43,10 +43,30 @@ npx agentsam init \
43
43
 
44
44
  ## Publish checklist
45
45
 
46
- 1. `npm test` passes
47
- 2. Bump version in `package.json`
48
- 3. `npm publish --access public`
49
- 4. Tag: `git tag v1.1.1 && git push origin v1.1.1`
46
+ 1. Root `package.json` name is **`@inneranimalmedia/agentsam-sdk`** (never a workspace kit)
47
+ 2. `npm test` passes
48
+ 3. Bump version in root `package.json`
49
+ 4. Confirm `files` includes consumer surfaces (`src`, `bin`, `python`, `protocol`, …)
50
+ 5. `npm publish --access public` (manual; requires `npm login` to org)
51
+ 6. Tag: `git tag vX.Y.Z && git push origin vX.Y.Z`
52
+ 7. Record pairing in [`docs/RELEASES.md`](./docs/RELEASES.md): `iam@<40-sha> ↔ @inneranimalmedia/agentsam-sdk@X.Y.Z`
53
+
54
+ Workspace packages under `packages/*` (e.g. `@inneranimalmedia/agentsam-shell-kit`) stay
55
+ `private: true` until separately ready — they are **not** the root publish identity.
56
+
57
+ ## Python tooling (`agentsam_sdk`)
58
+
59
+ Portable stdlib audits live under [`python/`](./python/) (import path `agentsam_sdk.*`).
60
+
61
+ ```bash
62
+ cd python
63
+ python3 -m pip install -e ".[dev]"
64
+ python3 -m pytest
65
+ python3 -m agentsam_sdk.repository.inventory --json --root /path/to/repo
66
+ ```
67
+
68
+ Inner Animal Media keeps a thin shim at `scripts/repo-size-inventory.py` that adds
69
+ `AGENTSAM_SDK_ROOT/python` (or a sibling/`~/agentsam-sdk` checkout) to `sys.path`.
50
70
 
51
71
  ## Known gaps (roadmap)
52
72
 
package/README.md CHANGED
@@ -6,6 +6,8 @@ Agent Sam is a full-stack autonomous agent SDK built on Cloudflare Workers, D1,
6
6
 
7
7
  **Repo:** [github.com/SamPrimeaux/agentsam-sdk](https://github.com/SamPrimeaux/agentsam-sdk) · **npm:** `@inneranimalmedia/agentsam-sdk`
8
8
 
9
+ **Protocol (dual-home, no drift):** [`protocol/README.md`](./protocol/README.md) — every tool/feature lands in this repo **and** `inneranimalmedia/agentsam-sdk/`; npm publish is manual after mirror. Python lane: [`python/`](./python/).
10
+
9
11
  ---
10
12
 
11
13
  ## What is Agent Sam?
@@ -0,0 +1,20 @@
1
+ # Agent Sam SDK — release pairings (IAM home)
2
+
3
+ **Status:** Evergreen receipts · **Product:** [README.md](./README.md)
4
+
5
+ Canonical npm-side table also lives in the SDK repo: `docs/RELEASES.md`.
6
+ **Keep both updated** when publishing (dual-home of the receipt).
7
+
8
+ | npm version | Published (UTC) | IAM git SHA (40) | Notes |
9
+ |-------------|-----------------|------------------|-------|
10
+ | 1.7.0 | 2026-07-14 | _(pre-receipt)_ | Prior publish |
11
+ | 1.9.0 | _(pending publish)_ | `fc1628505cb3c9946c149e4c468c2e264d6f381e` | `repository.inspect` + `--dupes`; mirrored from IAM agentsam-sdk |
12
+ | 1.8.0 | 2026-08-02T13:57:12.080Z | `580547301e370b77ec26bd72558979d335feedd9` | `python/` + `protocol/` in tarball; `repository.scan_bloat`; shell-kit under `packages/` |
13
+
14
+ **Latest pairing (ready to publish):** `iam@fc1628505cb3c9946c149e4c468c2e264d6f381e` ↔ `@inneranimalmedia/agentsam-sdk@1.9.0`
15
+
16
+ ### Receipt rules
17
+
18
+ - Full 40-char SHA only (AGENTS.md §3)
19
+ - Record the IAM SHA that introduced or mirrored the consumer-facing change
20
+ - Tag SDK repo `vX.Y.Z` on publish
@@ -0,0 +1,9 @@
1
+ # `@inneranimalmedia/agentsam-sdk` release receipts
2
+
3
+ | npm version | Published (UTC) | IAM git SHA (40) | Notes |
4
+ |-------------|-----------------|------------------|-------|
5
+ | 1.7.0 | 2026-07-14 | _(pre-receipt)_ | Prior publish |
6
+ | 1.9.0 | _(pending publish)_ | `fc1628505cb3c9946c149e4c468c2e264d6f381e` | `python/agentsam_sdk/repository/inspect.py` (`--dupes`); CLI `agentsam repository inspect` |
7
+ | 1.8.0 | 2026-08-02T13:57:12.080Z | `580547301e370b77ec26bd72558979d335feedd9` | `python/` + `protocol/` in npm `files`; `repository.scan_bloat`; shell-kit folded to `packages/agentsam-shell-kit` (private). SDK git at publish: `ac52669b89ddb16fad87c42fe53f2c93ef13bd1a`. |
8
+
9
+ **Pairing (ready to publish):** `iam@fc1628505cb3c9946c149e4c468c2e264d6f381e` ↔ `@inneranimalmedia/agentsam-sdk@1.9.0`
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@inneranimalmedia/agentsam-sdk",
3
- "version": "1.7.0",
4
- "description": "Agent Sam is a full-stack AI agent SDK for autonomous task execution covering data management, creative workflows, design commands, and multi-step agentic pipelines.",
3
+ "version": "1.9.0",
4
+ "description": "Agent Sam is a full-stack AI agent SDK for autonomous task execution \u2014 covering data management, creative workflows, design commands, and multi-step agentic pipelines.",
5
5
  "type": "module",
6
6
  "main": "./src/index.js",
7
7
  "exports": {
@@ -18,10 +18,15 @@
18
18
  "docs",
19
19
  "examples",
20
20
  "test",
21
+ "python",
22
+ "protocol",
23
+ "bin",
21
24
  "README.md",
22
25
  "LICENSE",
23
- "DEVELOPMENT.md",
24
- "bin"
26
+ "DEVELOPMENT.md"
27
+ ],
28
+ "workspaces": [
29
+ "packages/*"
25
30
  ],
26
31
  "scripts": {
27
32
  "test": "node test/smoke.mjs",
@@ -0,0 +1,51 @@
1
+ # agentsam-sdk protocol (LOCKED)
2
+
3
+ Every Agent Sam **tool / feature SDK surface** is dual-homed. There is no
4
+ “Python audits only in IAM” vs “JS CLI only on npm” split for productized
5
+ tooling.
6
+
7
+ | Home | Path | Role |
8
+ |------|------|------|
9
+ | **Main platform repo** | `inneranimalmedia/agentsam-sdk/` | Source of truth while building; ships with platform deploy |
10
+ | **Published SDK repo** | `github.com/SamPrimeaux/agentsam-sdk` → npm `@inneranimalmedia/agentsam-sdk` | Same contract + modules for external / CLI consumers |
11
+
12
+ ## Rules
13
+
14
+ 1. **Author once, land twice.** New `agentsam_sdk.*` tools (data, repository,
15
+ readiness, history, code-intel, …) land in the monorepo package **and** are
16
+ mirrored into the published `agentsam-sdk` repo in the same change set / PR
17
+ pair. No silent one-sided land.
18
+ 2. **Contract is shared.** `runtime/contract` (`ToolInput` / `ToolResult` /
19
+ receipts, unixepoch, no hardcoded identity) is the protocol. Language may
20
+ differ (Python stdlib vs JS), but CLI verbs and JSON shapes must stay
21
+ aligned — see `protocol/dual-repo-sync.md`.
22
+ 3. **No drift.** Publishing npm without updating the monorepo copy (or the
23
+ reverse) is a protocol violation. Gate: version bump + changelog note that
24
+ lists mirrored paths.
25
+ 4. **Ship is two-sided.**
26
+ - Platform: Mac `npm run deploy:full` / `deploy:fast` (or GCP `ship:remote`)
27
+ when the monorepo copy or Worker wiring changed.
28
+ - SDK: **manual** npm publish / version bump on `agentsam-sdk` after the
29
+ mirror lands (operator-owned — do not assume CI auto-publishes).
30
+ 5. **Inspiration ≠ copy-paste secrets.** In-app surfaces (e.g. `src/core/code-indexer.js`,
31
+ AST-RAG Phase 1/2) are the reference implementations to port into portable
32
+ SDK modules — strip platform-only bindings, keep adapters for D1 / git /
33
+ Hyperdrive.
34
+
35
+ ## Layout (both homes)
36
+
37
+ ```
38
+ agentsam-sdk/
39
+ protocol/ ← this law
40
+ python/agentsam_sdk ← Python portable tools (stdlib-first audits / inventory)
41
+ src/ ← JS CLI / scaffold (published npm entry)
42
+ packages/ ← optional workspace packages (not always in npm tarball)
43
+ agentsam-shell-kit/ ← @inneranimalmedia/agentsam-shell-kit (private until ready)
44
+ docs/gaps.md ← port status vs IAM scripts + in-app indexers
45
+ ```
46
+
47
+ **npm identity (LOCKED):** root publishable package is always
48
+ `@inneranimalmedia/agentsam-sdk`. Do not overwrite root `package.json` with a
49
+ workspace kit name. Fold UI kits under `packages/*`.
50
+
51
+ Exact folder names may evolve; the dual-home + root-identity rules do not.
@@ -0,0 +1,35 @@
1
+ # Dual-repo sync checklist
2
+
3
+ Use this every time you add or change an `agentsam_sdk` tool/feature.
4
+
5
+ ## Before coding
6
+
7
+ - [ ] Name the module (`agentsam_sdk.<domain>.<tool>`) and CLI verb
8
+ - [ ] Confirm it belongs in SDK (portable) vs platform-only Worker hot path
9
+ - [ ] Note in-app inspiration path (if any), e.g. `src/core/code-indexer.js`
10
+
11
+ ## Land
12
+
13
+ - [ ] Implement + tests in **`inneranimalmedia/agentsam-sdk/`**
14
+ - [ ] Mirror the same module/CLI/docs into **`agentsam-sdk`** (npm repo)
15
+ - [ ] Update `docs/gaps.md` (or equivalent) in **both** trees
16
+ - [ ] Update `protocol/` only in monorepo if law changed; copy README blurb to npm
17
+
18
+ ## Publish / deploy (operator)
19
+
20
+ - [ ] Platform: commit/push IAM → deploy by host (`deploy:full` / `ship:remote`)
21
+ - [ ] SDK: bump `package.json` version in npm repo → `npm publish` (manual)
22
+ - [ ] Record versions next to each other (PR description or receipt):
23
+ `iam@<sha>` ↔ `@inneranimalmedia/agentsam-sdk@<semver>`
24
+
25
+ ## Drift signals (fail the PR)
26
+
27
+ - Module exists only in one repo
28
+ - CLI flag / JSON field renamed on one side only
29
+ - README claims “this is not the npm package” / “audits don’t belong here”
30
+ - npm publish without a same-day IAM mirror commit (or documented lag ticket)
31
+
32
+ ## jq / host tools
33
+
34
+ Host tooling (`jq`, `wrangler`, Python ≥3.10) is documented in
35
+ `docs/tooling.md`. Keep that file mirrored when recipes change.
@@ -0,0 +1,12 @@
1
+ # agentsam_sdk (Python) — npm mirror
2
+
3
+ Exact dual-home of `inneranimalmedia/agentsam-sdk/`.
4
+
5
+ ```bash
6
+ cd python && pip install -e .
7
+ agentsam repository inventory --repo-root /path/to/repo --format json
8
+ python3 -m unittest discover -s tests -v
9
+ ./scripts/check-host-tooling.sh
10
+ ```
11
+
12
+ Protocol: [`../protocol/README.md`](../protocol/README.md). Do not advance this tree without mirroring the monorepo (or the reverse).
@@ -0,0 +1,9 @@
1
+ """agentsam_sdk — stdlib-only toolkit for inneranimalmedia D1/repo audits.
2
+
3
+ Every tool follows the runtime.contract pattern: ToolInput in, ToolResult out,
4
+ plus a receipt written to output_dir for audit trail. No hardcoded
5
+ identity/repo/workspace/tenant values anywhere in this package — see
6
+ runtime.contract.HARD_LAW_NOTE.
7
+ """
8
+
9
+ __version__ = "0.1.0"
@@ -0,0 +1,262 @@
1
+ """agentsam CLI -- stdlib argparse only (no click/typer dep, per project
2
+ convention: Python tooling here is stdlib-only).
3
+
4
+ agentsam data d1-bloat --quick --format markdown --output-dir /tmp/d1-bloat
5
+ agentsam data agentsam-walk --prefix agentsam_ --output-dir /tmp/walk
6
+ agentsam repository inventory --repo-root .. --output-dir /tmp/scan --format json
7
+ agentsam repository inventory --repo-root .. --format json | jq '.data.totals'
8
+ agentsam repository scan-bloat --root src --min-kb 10 --top 30 --format json
9
+ agentsam repository inspect --repo-root . --json --dupes | jq '.duplicates'
10
+ """
11
+ from __future__ import annotations
12
+
13
+ import argparse
14
+ import json
15
+ import sys
16
+
17
+ from agentsam_sdk.runtime.contract import ToolInput, ToolResult
18
+
19
+
20
+ def _print_result(result: ToolResult, fmt: str) -> None:
21
+ if fmt == "json":
22
+ print(json.dumps(result.to_dict(), indent=2, default=str))
23
+ else:
24
+ status = "OK" if result.ok else "FAIL"
25
+ print(f"[{status}] {result.tool} ({result.mode}) — {result.summary}", file=sys.stderr)
26
+ if result.error:
27
+ print(f" error: {result.error}", file=sys.stderr)
28
+ for a in result.artifacts:
29
+ print(f" wrote: {a}", file=sys.stderr)
30
+ # When markdown artifacts were written, surface path; else dump summary data keys
31
+ if fmt == "markdown" and result.ok and result.data.get("categories"):
32
+ # stdout stays quiet when artifacts exist; callers use the .md file
33
+ if not result.artifacts:
34
+ from agentsam_sdk.repository.inventory import _markdown
35
+
36
+ sys.stdout.write(_markdown(result.data))
37
+ if fmt == "markdown" and result.ok and result.data.get("files") is not None:
38
+ from agentsam_sdk.repository.scan_bloat import human_table
39
+
40
+ sys.stdout.write(
41
+ human_table(
42
+ result.data.get("files") or [],
43
+ scanned=int(result.data.get("file_count") or 0),
44
+ total_kb=float(result.data.get("total_kb") or 0),
45
+ total_tokens=int(result.data.get("total_est_tokens") or 0),
46
+ )
47
+ )
48
+
49
+
50
+ def _cmd_data_d1_bloat(args: argparse.Namespace) -> int:
51
+ from agentsam_sdk.data import d1_bloat
52
+
53
+ mode = "full" if args.full else "quick"
54
+ ti = ToolInput(
55
+ mode=mode,
56
+ params={
57
+ "db": args.db, "config": args.config, "repo_root": args.repo_root,
58
+ "prefix": args.prefix, "workers": args.workers, "top": args.top,
59
+ },
60
+ output_dir=args.output_dir,
61
+ )
62
+ result = d1_bloat.run(ti)
63
+ _print_result(result, args.format)
64
+ return 0 if result.ok else 1
65
+
66
+
67
+ def _cmd_data_agentsam_walk(args: argparse.Namespace) -> int:
68
+ from agentsam_sdk.data import agentsam_walk
69
+
70
+ ti = ToolInput(
71
+ mode="default",
72
+ params={"db": args.db, "config": args.config, "repo_root": args.repo_root, "prefix": args.prefix},
73
+ output_dir=args.output_dir,
74
+ )
75
+ result = agentsam_walk.run(ti)
76
+ _print_result(result, args.format)
77
+ return 0 if result.ok else 1
78
+
79
+
80
+ def _cmd_repository_inventory(args: argparse.Namespace) -> int:
81
+ from agentsam_sdk.repository import inventory
82
+
83
+ ti = ToolInput(
84
+ mode="read-only",
85
+ params={
86
+ "repo_root": args.repo_root,
87
+ "top": args.top,
88
+ "min_bytes": args.min_bytes,
89
+ "by_ext": args.by_ext,
90
+ "include_node_modules": args.include_node_modules,
91
+ "include_venvs": args.include_venvs,
92
+ "include_git": args.include_git,
93
+ "include_dist": args.include_dist,
94
+ "follow_symlinks": args.follow_symlinks,
95
+ },
96
+ output_dir=args.output_dir,
97
+ )
98
+ result = inventory.run(ti)
99
+ _print_result(result, args.format)
100
+ return 0 if result.ok else 1
101
+
102
+
103
+ def _cmd_repository_scan_bloat(args: argparse.Namespace) -> int:
104
+ from agentsam_sdk.repository import scan_bloat
105
+
106
+ ti = ToolInput(
107
+ mode="read-only",
108
+ params={
109
+ "root": args.root,
110
+ "top": args.top,
111
+ "min_kb": args.min_kb,
112
+ "ext": args.ext,
113
+ "exclude": args.exclude,
114
+ },
115
+ output_dir=args.output_dir,
116
+ )
117
+ result = scan_bloat.run(ti)
118
+ # Agent capture: --json-envelope prints data payload only (legacy tools/scan_bloat.py)
119
+ if args.json_envelope:
120
+ print(json.dumps(result.data if result.ok else {"ok": False, "error": result.error}, indent=2))
121
+ return 0 if result.ok else 1
122
+ _print_result(result, args.format)
123
+ return 0 if result.ok else 1
124
+
125
+
126
+ def _cmd_repository_inspect(args: argparse.Namespace) -> int:
127
+ from agentsam_sdk.repository import inspect as repo_inspect
128
+
129
+ argv: list[str] = []
130
+ if args.repo_root:
131
+ argv.extend(["--repo-root", args.repo_root])
132
+ want_text = bool(args.text) or args.format in ("text", "markdown")
133
+ want_json = bool(args.json) or args.format == "json" or not want_text
134
+ if want_json and not want_text:
135
+ argv.append("--json")
136
+ if want_text and not want_json:
137
+ argv.append("--text")
138
+ if want_text and want_json:
139
+ # Explicit both → JSON wins (machine default) unless only --text
140
+ argv.append("--json")
141
+ if args.dupes:
142
+ argv.append("--dupes")
143
+ if args.all:
144
+ argv.append("--all")
145
+ if args.since:
146
+ argv.extend(["--since", args.since])
147
+ if args.recent is not None:
148
+ argv.extend(["--recent", str(args.recent)])
149
+ if args.largest is not None:
150
+ argv.extend(["--largest", str(args.largest)])
151
+ if args.out:
152
+ argv.extend(["--out", args.out])
153
+ return repo_inspect.main_cli(argv)
154
+
155
+
156
+ def build_parser() -> argparse.ArgumentParser:
157
+ ap = argparse.ArgumentParser(prog="agentsam", description="agentsam_sdk CLI")
158
+ sub = ap.add_subparsers(dest="group", required=True)
159
+
160
+ data = sub.add_parser("data", help="D1 data audits")
161
+ data_sub = data.add_subparsers(dest="cmd", required=True)
162
+
163
+ bloat = data_sub.add_parser(
164
+ "d1-bloat",
165
+ help="database-scoped D1 audit (--quick=counts, --full=text sizes)",
166
+ )
167
+ bloat.add_argument("--db", help="D1 database name (else AGENTSAM_D1_DB_NAME)")
168
+ bloat.add_argument("--config", help="wrangler config path (else AGENTSAM_WRANGLER_CONFIG)")
169
+ bloat.add_argument("--repo-root", help="repo root wrangler runs from")
170
+ bloat.add_argument("--quick", action="store_true", default=True,
171
+ help="All tables: COUNT(*) only (default)")
172
+ bloat.add_argument("--full", action="store_true",
173
+ help="All tables: row counts + text/JSON LENGTH estimates")
174
+ bloat.add_argument("--prefix", help="Only tables whose name starts with this prefix")
175
+ bloat.add_argument("--workers", type=int, default=6)
176
+ bloat.add_argument("--top", type=int, default=40)
177
+ bloat.add_argument("--output-dir")
178
+ bloat.add_argument("--format", choices=["json", "markdown"], default="markdown")
179
+ bloat.set_defaults(func=_cmd_data_d1_bloat)
180
+
181
+ walk = data_sub.add_parser("agentsam-walk", help="walk agentsam_* tables")
182
+ walk.add_argument("--db")
183
+ walk.add_argument("--config")
184
+ walk.add_argument("--repo-root")
185
+ walk.add_argument("--prefix", default="agentsam_")
186
+ walk.add_argument("--output-dir")
187
+ walk.add_argument("--format", choices=["json", "markdown"], default="markdown")
188
+ walk.set_defaults(func=_cmd_data_agentsam_walk)
189
+
190
+ repo = sub.add_parser("repository", help="repository-level audits")
191
+ repo_sub = repo.add_subparsers(dest="cmd", required=True)
192
+ inv = repo_sub.add_parser(
193
+ "inventory",
194
+ help="file counts + sizes by logical category (jq-friendly JSON)",
195
+ )
196
+ inv.add_argument("--repo-root", default=".")
197
+ inv.add_argument("--output-dir")
198
+ inv.add_argument("--format", choices=["json", "markdown"], default="json")
199
+ inv.add_argument("--top", type=int, default=20, help="N largest files (0 to omit)")
200
+ inv.add_argument("--min-bytes", type=int, default=0)
201
+ inv.add_argument(
202
+ "--by-ext",
203
+ action=argparse.BooleanOptionalAction,
204
+ default=True,
205
+ help="Include by_extension_detail with byte rollups (default: on)",
206
+ )
207
+ inv.add_argument("--include-node-modules", action="store_true")
208
+ inv.add_argument("--include-venvs", action="store_true")
209
+ inv.add_argument("--include-git", action="store_true")
210
+ inv.add_argument("--include-dist", action="store_true")
211
+ inv.add_argument("--follow-symlinks", action="store_true")
212
+ inv.set_defaults(func=_cmd_repository_inventory)
213
+
214
+ sb = repo_sub.add_parser(
215
+ "scan-bloat",
216
+ help="largest runtime source files (KB/lines/est. tokens)",
217
+ )
218
+ sb.add_argument("--root", default=".", help="directory to scan (default: cwd)")
219
+ sb.add_argument("--top", type=int, default=30)
220
+ sb.add_argument("--min-kb", type=float, default=0)
221
+ sb.add_argument(
222
+ "--ext",
223
+ default=".js,.ts,.jsx,.tsx,.mjs,.cjs",
224
+ help="comma-separated extensions",
225
+ )
226
+ sb.add_argument("--exclude", default="", help="extra dir names to exclude")
227
+ sb.add_argument("--output-dir")
228
+ sb.add_argument("--format", choices=["json", "markdown"], default="markdown")
229
+ sb.add_argument(
230
+ "--json-envelope",
231
+ action="store_true",
232
+ help="print ToolResult.data JSON only (agent/terminal capture)",
233
+ )
234
+ sb.set_defaults(func=_cmd_repository_scan_bloat)
235
+
236
+ insp = repo_sub.add_parser(
237
+ "inspect",
238
+ help="file walk: sizes + dates (+ optional --dupes SHA-256 groups)",
239
+ )
240
+ insp.add_argument("--repo-root", default=None)
241
+ insp.add_argument("--format", choices=["json", "markdown", "text"], default="json")
242
+ insp.add_argument("--json", action="store_true")
243
+ insp.add_argument("--text", action="store_true")
244
+ insp.add_argument("--dupes", action="store_true")
245
+ insp.add_argument("--all", action="store_true")
246
+ insp.add_argument("--since", default=None)
247
+ insp.add_argument("--recent", type=int, default=50)
248
+ insp.add_argument("--largest", type=int, default=30)
249
+ insp.add_argument("--out", default=None)
250
+ insp.set_defaults(func=_cmd_repository_inspect)
251
+
252
+ return ap
253
+
254
+
255
+ def main(argv: list[str] | None = None) -> int:
256
+ parser = build_parser()
257
+ args = parser.parse_args(argv)
258
+ return args.func(args)
259
+
260
+
261
+ if __name__ == "__main__":
262
+ sys.exit(main())
File without changes
@@ -0,0 +1,157 @@
1
+ """agentsam_sdk.data.agentsam_walk -- condensed port of
2
+ scripts/walk_agentsam_tables.py (+ schema-focus of scripts/d1_schema_audit.py).
3
+
4
+ Walks every `agentsam_%` sqlite_master object: schema, indexes, foreign
5
+ keys, row count, freshness (via *_at / *_at_epoch columns if present), and
6
+ groups tables into capability buckets by name-substring heuristics.
7
+
8
+ Note: this is a condensed re-implementation, not a byte-for-byte port of the
9
+ 801-line original -- see docs/gaps.md for what's folded in vs deferred
10
+ (duplicate-detection heuristics, per-feature markdown chunking into db/*.md
11
+ files a la d1_schema_audit.py are deferred to a follow-up pass).
12
+ """
13
+ from __future__ import annotations
14
+
15
+ from dataclasses import dataclass, field, asdict
16
+ from datetime import datetime, timezone
17
+ from pathlib import Path
18
+ from typing import Optional
19
+
20
+ from agentsam_sdk.data.d1_adapter import D1Adapter, D1AdapterError
21
+ from agentsam_sdk.runtime.contract import ToolInput, ToolResult, write_receipt, start_timer
22
+
23
+ TOOL_NAME = "data.agentsam_walk"
24
+
25
+ CAPABILITY_RULES: dict[str, list[str]] = {
26
+ "agent_run_spine": ["run", "runs", "execution", "executions", "session", "sessions", "step", "steps", "message", "messages"],
27
+ "model_routing": ["model", "catalog", "routing", "route", "routes", "arm", "arms", "requirement", "provider", "prompt"],
28
+ "tools_commands_mcp": ["tool", "tools", "mcp", "command", "commands", "skill", "skills", "invocation", "chain", "script", "scripts"],
29
+ "workflow_dag": ["workflow", "workflows", "node", "nodes", "edge", "edges", "approval", "approvals", "task", "tasks"],
30
+ "memory_rag": ["memory", "embedding", "vector", "chunk", "chunks", "document", "documents", "rag"],
31
+ "observability": ["log", "logs", "trace", "traces", "metric", "metrics", "health", "analytics", "otlp", "error", "errors"],
32
+ "cms_content": ["cms_", "page", "pages", "content", "nav", "template"],
33
+ "security_policy": ["policy", "policies", "guardrail", "trusted", "origin", "auth", "credential"],
34
+ }
35
+
36
+
37
+ @dataclass
38
+ class TableWalk:
39
+ name: str
40
+ columns: list[dict] = field(default_factory=list)
41
+ indexes: list[dict] = field(default_factory=list)
42
+ foreign_keys: list[dict] = field(default_factory=list)
43
+ row_count: int = 0
44
+ has_freshness_col: bool = False
45
+ capability: str = "uncategorized"
46
+ error: Optional[str] = None
47
+ flags: list[str] = field(default_factory=list)
48
+
49
+
50
+ def _capability_for(table: str) -> str:
51
+ """Score by total matched-keyword *length*, not match count -- a single
52
+ specific hit ("workflow") should outrank two generic ones ("run", "runs").
53
+ """
54
+ lname = table.lower()
55
+ best, best_score = "uncategorized", 0
56
+ for cap, keywords in CAPABILITY_RULES.items():
57
+ score = sum(len(kw) for kw in keywords if kw in lname)
58
+ if score > best_score:
59
+ best, best_score = cap, score
60
+ return best
61
+
62
+
63
+ def _walk_table(adapter: D1Adapter, table: str) -> TableWalk:
64
+ tw = TableWalk(name=table, capability=_capability_for(table))
65
+ try:
66
+ cols = adapter.table_columns(table)
67
+ tw.columns = [{"name": n, "type": t} for n, t in cols]
68
+ tw.indexes = adapter.table_indexes(table)
69
+ tw.foreign_keys = adapter.foreign_keys(table)
70
+ tw.row_count = adapter.row_count(table)
71
+ tw.has_freshness_col = any(
72
+ n.lower() in ("updated_at", "created_at", "updated_at_epoch", "created_at_epoch")
73
+ for n, _ in cols
74
+ )
75
+ if tw.row_count == 0:
76
+ tw.flags.append("empty")
77
+ if not tw.has_freshness_col:
78
+ tw.flags.append("no_freshness_column")
79
+ if not tw.indexes and tw.row_count > 1000:
80
+ tw.flags.append("no_index_high_row_count")
81
+ except D1AdapterError as e:
82
+ tw.error = str(e)[:200]
83
+ except Exception as e: # noqa: BLE001
84
+ tw.error = str(e)[:200]
85
+ return tw
86
+
87
+
88
+ def _render_markdown(walks: list[TableWalk], prefix: str) -> str:
89
+ lines = [
90
+ f"# agentsam walk -- `{prefix}%` objects",
91
+ "",
92
+ f"- **Generated:** {datetime.now(timezone.utc).strftime('%Y-%m-%d %H:%M UTC')}",
93
+ f"- **Tables walked:** {len(walks)}",
94
+ "",
95
+ "## By capability",
96
+ "",
97
+ ]
98
+ by_cap: dict[str, list[TableWalk]] = {}
99
+ for w in walks:
100
+ by_cap.setdefault(w.capability, []).append(w)
101
+ for cap in sorted(by_cap):
102
+ lines.append(f"### {cap} ({len(by_cap[cap])})")
103
+ lines.append("")
104
+ lines.append("| Table | Rows | Indexes | FKs | Flags |")
105
+ lines.append("|-------|------|---------|-----|-------|")
106
+ for w in sorted(by_cap[cap], key=lambda x: x.row_count, reverse=True):
107
+ flags = ", ".join(w.flags) or "-"
108
+ err = f" ⚠ {w.error}" if w.error else ""
109
+ lines.append(f"| `{w.name}` | {w.row_count:,} | {len(w.indexes)} | {len(w.foreign_keys)} | {flags}{err} |")
110
+ lines.append("")
111
+ return "\n".join(lines)
112
+
113
+
114
+ def run(tool_input: ToolInput) -> ToolResult:
115
+ started = start_timer()
116
+ p = tool_input.params
117
+ prefix = p.get("prefix", "agentsam_")
118
+ output_dir = tool_input.output_path()
119
+
120
+ try:
121
+ adapter = D1Adapter.from_env(
122
+ db_name=p.get("db"), wrangler_config=p.get("config"), repo_root=p.get("repo_root")
123
+ )
124
+ tables = adapter.list_tables(like=f"{prefix}%")
125
+ walks = [_walk_table(adapter, t) for t in tables]
126
+ md = _render_markdown(walks, prefix)
127
+ json_payload = {
128
+ "prefix": prefix,
129
+ "database": adapter.db_name,
130
+ "table_count": len(walks),
131
+ "tables": [asdict(w) for w in walks],
132
+ }
133
+
134
+ artifacts: list[str] = []
135
+ if output_dir:
136
+ output_dir.mkdir(parents=True, exist_ok=True)
137
+ (output_dir / "agentsam-walk.md").write_text(md, encoding="utf-8")
138
+ (output_dir / "agentsam-walk.json").write_text(
139
+ __import__("json").dumps(json_payload, indent=2), encoding="utf-8"
140
+ )
141
+ artifacts = [str(output_dir / "agentsam-walk.md"), str(output_dir / "agentsam-walk.json")]
142
+
143
+ result = ToolResult(
144
+ ok=True, tool=TOOL_NAME, mode=tool_input.mode, request_id=tool_input.request_id,
145
+ started_at=started, finished_at=start_timer(),
146
+ summary=f"Walked {len(walks)} `{prefix}%` tables.",
147
+ data=json_payload, artifacts=artifacts,
148
+ )
149
+ except D1AdapterError as e:
150
+ result = ToolResult(
151
+ ok=False, tool=TOOL_NAME, mode=tool_input.mode, request_id=tool_input.request_id,
152
+ started_at=started, finished_at=start_timer(),
153
+ summary="D1 adapter error", error=str(e),
154
+ )
155
+
156
+ write_receipt(result, output_dir)
157
+ return result