si-deckhand 0.1.0__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.
@@ -0,0 +1,106 @@
1
+ Metadata-Version: 2.4
2
+ Name: si-deckhand
3
+ Version: 0.1.0
4
+ Summary: Lightweight local agent that indexes, retrieves, and wikis the workspace. The crew member who knows where everything is.
5
+ Author: SuperInstance
6
+ License-Expression: MIT
7
+ Keywords: agent,retriever,wiki,index,exo-filed,local
8
+ Classifier: Development Status :: 3 - Alpha
9
+ Classifier: Intended Audience :: Developers
10
+ Classifier: Programming Language :: Python :: 3
11
+ Classifier: Topic :: System :: Systems Administration
12
+ Classifier: Topic :: Text Processing :: Indexing
13
+ Requires-Python: >=3.10
14
+ Description-Content-Type: text/markdown
15
+ Provides-Extra: vector
16
+ Requires-Dist: numpy>=1.20; extra == "vector"
17
+ Provides-Extra: dev
18
+ Requires-Dist: pytest>=7.0; extra == "dev"
19
+ Requires-Dist: pytest-cov; extra == "dev"
20
+
21
+ # deckhand
22
+
23
+ *The crew member who knows where everything is.*
24
+
25
+ ---
26
+
27
+ **deckhand** is a lightweight local agent that indexes, retrieves, and wikis your workspace. File-first (exo-filed). Human-readable. No hidden database.
28
+
29
+ ## What it does
30
+
31
+ - **Indexes** every file in your workspace (code, docs, configs)
32
+ - **Builds wiki pages** that a human can `cat` — REPOS.md, CONNECTIONS.md, GLOSSARY.md, STATUS.md
33
+ - **Searches** via BM25 (pure Python, no dependencies)
34
+ - **Batches tasks** from PLANNER_QUEUE.md, OPEN_QUESTIONS.md, and recent file changes
35
+ - **Studies** repos off-watch — writes daily observation logs
36
+ - **Runs forever** in a loop: index → wiki → batch → study → sleep → repeat
37
+
38
+ ## Install
39
+
40
+ ```bash
41
+ pip install deckhand
42
+ ```
43
+
44
+ ## Use
45
+
46
+ ```bash
47
+ # Index the workspace and generate wiki
48
+ deckhand index /path/to/workspace
49
+
50
+ # Search for something
51
+ deckhand search /path/to/workspace "conservation law"
52
+
53
+ # Generate just the wiki
54
+ deckhand wiki /path/to/workspace
55
+
56
+ # Collect tasks into a batch
57
+ deckhand batch /path/to/workspace
58
+
59
+ # Run the deckhand loop forever (index → wiki → batch → study → sleep)
60
+ deckhand run /path/to/workspace --sleep 300
61
+ ```
62
+
63
+ ## The wiki output
64
+
65
+ After indexing, deckhand writes to `deckhand-wiki/`:
66
+
67
+ - **REPOS.md** — table of every repo, one line each. Health indicators (✅ ok, ⚠️ untested, 🔩 stub, ❌ no-readme)
68
+ - **CONNECTIONS.md** — cross-reference graph. Which repos reference which.
69
+ - **GLOSSARY.md** — top 100 terms across the workspace
70
+ - **STATUS.md** — health summary. Lists repos that need tests, need READMEs, or may be abandoned
71
+ - **INDEX.md** — machine-readable JSON index
72
+ - **BATCH.md** — collected tasks from PLANNER_QUEUE + OPEN_QUESTIONS + recent changes
73
+ - **logs/YYYY-MM-DD.md** — daily study observations
74
+
75
+ ## Why exo-filed?
76
+
77
+ A vector database is opaque. You can't `cat` it. You can't grep it. You can't diff it in git.
78
+
79
+ deckhand's index is markdown + JSON. A human can open REPOS.md and understand the fleet in 30 seconds. An agent can read CONNECTIONS.md instead of re-reading 4,000 repos.
80
+
81
+ When the corpus gets too large for file-based search (>10K files), build the optional vector twin:
82
+
83
+ ```python
84
+ from deckhand.vector_twin import VectorTwin # coming in v0.2
85
+ ```
86
+
87
+ The vector twin is ALWAYS accompanied by its file-twin. You can always fall back to the human-readable version.
88
+
89
+ ## The maritime metaphor
90
+
91
+ On a boat, the deckhand is the crew member who:
92
+ - Knows where every line, every shackle, every tool is stored
93
+ - Prepares tasks for the captain (batches)
94
+ - Maintains the logbook (study logs)
95
+ - Studies charts off-watch (studies repos when idle)
96
+ - Doesn't need to be told what to do — knows the direction of the voyage
97
+
98
+ The deckhand doesn't replace the captain (the director model). The deckhand makes the captain more efficient by pre-processing the workspace into a form the captain can act on in fewer calls.
99
+
100
+ ## License
101
+
102
+ MIT
103
+
104
+ ---
105
+
106
+ *Built by SuperInstance. The deckhand knows where everything is.*
@@ -0,0 +1,86 @@
1
+ # deckhand
2
+
3
+ *The crew member who knows where everything is.*
4
+
5
+ ---
6
+
7
+ **deckhand** is a lightweight local agent that indexes, retrieves, and wikis your workspace. File-first (exo-filed). Human-readable. No hidden database.
8
+
9
+ ## What it does
10
+
11
+ - **Indexes** every file in your workspace (code, docs, configs)
12
+ - **Builds wiki pages** that a human can `cat` — REPOS.md, CONNECTIONS.md, GLOSSARY.md, STATUS.md
13
+ - **Searches** via BM25 (pure Python, no dependencies)
14
+ - **Batches tasks** from PLANNER_QUEUE.md, OPEN_QUESTIONS.md, and recent file changes
15
+ - **Studies** repos off-watch — writes daily observation logs
16
+ - **Runs forever** in a loop: index → wiki → batch → study → sleep → repeat
17
+
18
+ ## Install
19
+
20
+ ```bash
21
+ pip install deckhand
22
+ ```
23
+
24
+ ## Use
25
+
26
+ ```bash
27
+ # Index the workspace and generate wiki
28
+ deckhand index /path/to/workspace
29
+
30
+ # Search for something
31
+ deckhand search /path/to/workspace "conservation law"
32
+
33
+ # Generate just the wiki
34
+ deckhand wiki /path/to/workspace
35
+
36
+ # Collect tasks into a batch
37
+ deckhand batch /path/to/workspace
38
+
39
+ # Run the deckhand loop forever (index → wiki → batch → study → sleep)
40
+ deckhand run /path/to/workspace --sleep 300
41
+ ```
42
+
43
+ ## The wiki output
44
+
45
+ After indexing, deckhand writes to `deckhand-wiki/`:
46
+
47
+ - **REPOS.md** — table of every repo, one line each. Health indicators (✅ ok, ⚠️ untested, 🔩 stub, ❌ no-readme)
48
+ - **CONNECTIONS.md** — cross-reference graph. Which repos reference which.
49
+ - **GLOSSARY.md** — top 100 terms across the workspace
50
+ - **STATUS.md** — health summary. Lists repos that need tests, need READMEs, or may be abandoned
51
+ - **INDEX.md** — machine-readable JSON index
52
+ - **BATCH.md** — collected tasks from PLANNER_QUEUE + OPEN_QUESTIONS + recent changes
53
+ - **logs/YYYY-MM-DD.md** — daily study observations
54
+
55
+ ## Why exo-filed?
56
+
57
+ A vector database is opaque. You can't `cat` it. You can't grep it. You can't diff it in git.
58
+
59
+ deckhand's index is markdown + JSON. A human can open REPOS.md and understand the fleet in 30 seconds. An agent can read CONNECTIONS.md instead of re-reading 4,000 repos.
60
+
61
+ When the corpus gets too large for file-based search (>10K files), build the optional vector twin:
62
+
63
+ ```python
64
+ from deckhand.vector_twin import VectorTwin # coming in v0.2
65
+ ```
66
+
67
+ The vector twin is ALWAYS accompanied by its file-twin. You can always fall back to the human-readable version.
68
+
69
+ ## The maritime metaphor
70
+
71
+ On a boat, the deckhand is the crew member who:
72
+ - Knows where every line, every shackle, every tool is stored
73
+ - Prepares tasks for the captain (batches)
74
+ - Maintains the logbook (study logs)
75
+ - Studies charts off-watch (studies repos when idle)
76
+ - Doesn't need to be told what to do — knows the direction of the voyage
77
+
78
+ The deckhand doesn't replace the captain (the director model). The deckhand makes the captain more efficient by pre-processing the workspace into a form the captain can act on in fewer calls.
79
+
80
+ ## License
81
+
82
+ MIT
83
+
84
+ ---
85
+
86
+ *Built by SuperInstance. The deckhand knows where everything is.*
@@ -0,0 +1,35 @@
1
+ [build-system]
2
+ requires = ["setuptools>=68.0", "wheel"]
3
+ build-backend = "setuptools.build_meta"
4
+
5
+ [project]
6
+ name = "si-deckhand"
7
+ version = "0.1.0"
8
+ description = "Lightweight local agent that indexes, retrieves, and wikis the workspace. The crew member who knows where everything is."
9
+ readme = "README.md"
10
+ license = "MIT"
11
+ requires-python = ">=3.10"
12
+ authors = [{ name = "SuperInstance" }]
13
+ keywords = ["agent", "retriever", "wiki", "index", "exo-filed", "local"]
14
+ classifiers = [
15
+ "Development Status :: 3 - Alpha",
16
+ "Intended Audience :: Developers",
17
+ "Programming Language :: Python :: 3",
18
+ "Topic :: System :: Systems Administration",
19
+ "Topic :: Text Processing :: Indexing",
20
+ ]
21
+
22
+ dependencies = []
23
+
24
+ [project.optional-dependencies]
25
+ vector = ["numpy>=1.20"]
26
+ dev = ["pytest>=7.0", "pytest-cov"]
27
+
28
+ [project.scripts]
29
+ deckhand = "deckhand.cli:main"
30
+
31
+ [tool.setuptools.packages.find]
32
+ where = ["src"]
33
+
34
+ [tool.pytest.ini_options]
35
+ testpaths = ["tests"]
@@ -0,0 +1,4 @@
1
+ [egg_info]
2
+ tag_build =
3
+ tag_date = 0
4
+
@@ -0,0 +1,23 @@
1
+ """
2
+ deckhand — Lightweight local agent that indexes, retrieves, and wikis the workspace.
3
+
4
+ The deckhand is the crew member who knows where everything is.
5
+ File-first (exo-filed). Human-readable. No hidden vector DB.
6
+ """
7
+
8
+ from __future__ import annotations
9
+
10
+ __version__ = "0.1.0"
11
+ __all__ = [
12
+ "Indexer",
13
+ "Retriever",
14
+ "WikiGenerator",
15
+ "TaskBatcher",
16
+ "Deckhand",
17
+ ]
18
+
19
+ from deckhand.agent import Deckhand
20
+ from deckhand.indexer import Indexer
21
+ from deckhand.retriever import Retriever
22
+ from deckhand.wiki import WikiGenerator
23
+ from deckhand.task_batcher import TaskBatcher
@@ -0,0 +1,143 @@
1
+ """Agent — the main deckhand loop.
2
+
3
+ Index → Study → Batch → Sleep → Repeat.
4
+ When no tasks are queued, study the workspace and update the wiki.
5
+ """
6
+
7
+ from __future__ import annotations
8
+
9
+ import time
10
+ from pathlib import Path
11
+ from typing import Any
12
+
13
+ from deckhand.indexer import Indexer
14
+ from deckhand.retriever import Retriever
15
+ from deckhand.wiki import WikiGenerator
16
+ from deckhand.task_batcher import TaskBatcher
17
+
18
+
19
+ class Deckhand:
20
+ """The main agent loop."""
21
+
22
+ def __init__(
23
+ self,
24
+ workspace_root: str,
25
+ wiki_dir: str | None = None,
26
+ sleep_seconds: float = 300.0,
27
+ ) -> None:
28
+ self.root = workspace_root
29
+ self.wiki_dir = wiki_dir or str(Path(workspace_root) / "deckhand-wiki")
30
+ self.sleep_seconds = sleep_seconds
31
+ self.indexer = Indexer(workspace_root)
32
+ self.retriever: Retriever | None = None
33
+ self.wiki_gen: WikiGenerator | None = None
34
+ self.batcher = TaskBatcher(workspace_root)
35
+ self._running = False
36
+
37
+ def index(self) -> dict[str, Any]:
38
+ """Run a full index pass."""
39
+ stats = self.indexer.index()
40
+ self.retriever = Retriever(self.indexer.files)
41
+ self.wiki_gen = WikiGenerator(self.indexer)
42
+ return stats
43
+
44
+ def search(self, query: str, top_k: int = 10) -> list[dict[str, Any]]:
45
+ """Search the workspace."""
46
+ if not self.retriever:
47
+ self.index()
48
+ assert self.retriever is not None
49
+ results = self.retriever.search(query, top_k=top_k)
50
+ return [r.to_dict() for r in results]
51
+
52
+ def generate_wiki(self) -> list[str]:
53
+ """Generate all wiki pages."""
54
+ if not self.wiki_gen:
55
+ self.index()
56
+ assert self.wiki_gen is not None
57
+ return self.wiki_gen.generate(self.wiki_dir)
58
+
59
+ def batch_tasks(self) -> str:
60
+ """Collect and batch tasks."""
61
+ batch_path = str(Path(self.wiki_dir) / "BATCH.md")
62
+ return self.batcher.write_batch(batch_path)
63
+
64
+ def study(self) -> str:
65
+ """Study a random repo and write observations."""
66
+ if not self.indexer.repos:
67
+ self.index()
68
+
69
+ # Pick a repo that hasn't been studied recently
70
+ repos_by_age = sorted(
71
+ self.indexer.repos.values(),
72
+ key=lambda r: r.last_modified,
73
+ )
74
+ if not repos_by_age:
75
+ return "nothing to study"
76
+
77
+ target = repos_by_age[0] # oldest modified
78
+
79
+ log_dir = Path(self.wiki_dir) / "logs"
80
+ log_dir.mkdir(parents=True, exist_ok=True)
81
+ log_path = log_dir / f"{time.strftime('%Y-%m-%d')}.md"
82
+
83
+ lines = [
84
+ f"# Deckhand Study Log — {time.strftime('%Y-%m-%d')}",
85
+ f"",
86
+ f"## Studied: `{target.name}`",
87
+ f"",
88
+ f"- Files: {target.file_count}",
89
+ f"- Health: {target.health}",
90
+ f"- Has tests: {target.has_tests}",
91
+ f"- Has README: {target.has_readme}",
92
+ f"- Languages: {target.languages}",
93
+ f"",
94
+ ]
95
+
96
+ if target.health == "untested":
97
+ lines.append("⚠️ This repo has code but no tests. Needs a test suite.")
98
+ elif target.health == "no-readme":
99
+ lines.append("❌ This repo has no README. Needs documentation.")
100
+ elif target.health == "stub":
101
+ lines.append("🔩 This repo is a stub (< 3 files). May be abandoned.")
102
+ else:
103
+ lines.append("✅ This repo looks healthy.")
104
+
105
+ lines.append("")
106
+
107
+ with open(log_path, "a") as fh:
108
+ fh.write("\n".join(lines))
109
+
110
+ return str(log_path)
111
+
112
+ def run_once(self) -> dict[str, Any]:
113
+ """Run one full cycle: index → wiki → batch → study."""
114
+ stats = self.index()
115
+ wiki_files = self.generate_wiki()
116
+ batch_path = self.batch_tasks()
117
+ study_log = self.study()
118
+
119
+ return {
120
+ "index": stats,
121
+ "wiki_files": wiki_files,
122
+ "batch": batch_path,
123
+ "study_log": study_log,
124
+ }
125
+
126
+ def run_forever(self) -> None:
127
+ """Run the main loop forever."""
128
+ self._running = True
129
+ while self._running:
130
+ try:
131
+ result = self.run_once()
132
+ print(
133
+ f"[deckhand] indexed {result['index']['total_files']} files, "
134
+ f"wrote {len(result['wiki_files'])} wiki pages, "
135
+ f"batch at {result['batch']}"
136
+ )
137
+ except Exception as e:
138
+ print(f"[deckhand] error: {e}")
139
+
140
+ time.sleep(self.sleep_seconds)
141
+
142
+ def stop(self) -> None:
143
+ self._running = False
@@ -0,0 +1,98 @@
1
+ """CLI for deckhand."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import argparse
6
+ import sys
7
+
8
+ from deckhand.agent import Deckhand
9
+
10
+
11
+ def main(argv: list[str] | None = None) -> int:
12
+ parser = argparse.ArgumentParser(
13
+ prog="deckhand",
14
+ description="🐑 The deckhand — indexes, retrieves, and wikis the workspace.",
15
+ )
16
+
17
+ sub = parser.add_subparsers(dest="command")
18
+
19
+ # index
20
+ p_index = sub.add_parser("index", help="Index the workspace")
21
+ p_index.add_argument("workspace", help="Workspace root directory")
22
+ p_index.add_argument("--wiki", default=None, help="Wiki output directory")
23
+
24
+ # search
25
+ p_search = sub.add_parser("search", help="Search the workspace")
26
+ p_search.add_argument("workspace", help="Workspace root directory")
27
+ p_search.add_argument("query", help="Search query")
28
+ p_search.add_argument("--top", type=int, default=10, help="Number of results")
29
+
30
+ # wiki
31
+ p_wiki = sub.add_parser("wiki", help="Generate wiki pages")
32
+ p_wiki.add_argument("workspace", help="Workspace root directory")
33
+ p_wiki.add_argument("--output", default=None, help="Wiki output directory")
34
+
35
+ # batch
36
+ p_batch = sub.add_parser("batch", help="Collect and batch tasks")
37
+ p_batch.add_argument("workspace", help="Workspace root directory")
38
+
39
+ # study
40
+ p_study = sub.add_parser("study", help="Study a repo and write observations")
41
+ p_study.add_argument("workspace", help="Workspace root directory")
42
+
43
+ # run
44
+ p_run = sub.add_parser("run", help="Run the deckhand loop forever")
45
+ p_run.add_argument("workspace", help="Workspace root directory")
46
+ p_run.add_argument("--sleep", type=float, default=300, help="Sleep seconds between cycles")
47
+
48
+ args = parser.parse_args(argv)
49
+
50
+ if not args.command:
51
+ parser.print_help()
52
+ return 0
53
+
54
+ dh = Deckhand(
55
+ workspace_root=args.workspace,
56
+ wiki_dir=getattr(args, "wiki", None) or getattr(args, "output", None),
57
+ sleep_seconds=getattr(args, "sleep", 300),
58
+ )
59
+
60
+ if args.command == "index":
61
+ stats = dh.index()
62
+ print(f"Indexed {stats['total_files']} files across {stats['total_repos']} repos in {stats['elapsed_seconds']}s")
63
+ dh.generate_wiki()
64
+ print(f"Wiki written to {dh.wiki_dir}")
65
+
66
+ elif args.command == "search":
67
+ results = dh.search(args.query, top_k=args.top)
68
+ if not results:
69
+ print("No results found.")
70
+ for i, r in enumerate(results, 1):
71
+ print(f"{i}. [{r['score']:.2f}] {r['rel_path']}")
72
+ if r["snippet"]:
73
+ print(f" {r['snippet'][:150]}...")
74
+
75
+ elif args.command == "wiki":
76
+ dh.index()
77
+ files = dh.generate_wiki()
78
+ print(f"Wiki pages written:")
79
+ for f in files:
80
+ print(f" {f}")
81
+
82
+ elif args.command == "batch":
83
+ path = dh.batch_tasks()
84
+ print(f"Batch written to {path}")
85
+
86
+ elif args.command == "study":
87
+ log = dh.study()
88
+ print(f"Study log written to {log}")
89
+
90
+ elif args.command == "run":
91
+ print(f"Deckhand running on {args.workspace} (sleep={args.sleep}s)")
92
+ dh.run_forever()
93
+
94
+ return 0
95
+
96
+
97
+ if __name__ == "__main__":
98
+ sys.exit(main())