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.
- si_deckhand-0.1.0/PKG-INFO +106 -0
- si_deckhand-0.1.0/README.md +86 -0
- si_deckhand-0.1.0/pyproject.toml +35 -0
- si_deckhand-0.1.0/setup.cfg +4 -0
- si_deckhand-0.1.0/src/deckhand/__init__.py +23 -0
- si_deckhand-0.1.0/src/deckhand/agent.py +143 -0
- si_deckhand-0.1.0/src/deckhand/cli.py +98 -0
- si_deckhand-0.1.0/src/deckhand/indexer.py +309 -0
- si_deckhand-0.1.0/src/deckhand/retriever.py +146 -0
- si_deckhand-0.1.0/src/deckhand/task_batcher.py +129 -0
- si_deckhand-0.1.0/src/deckhand/wiki.py +209 -0
- si_deckhand-0.1.0/src/si_deckhand.egg-info/PKG-INFO +106 -0
- si_deckhand-0.1.0/src/si_deckhand.egg-info/SOURCES.txt +18 -0
- si_deckhand-0.1.0/src/si_deckhand.egg-info/dependency_links.txt +1 -0
- si_deckhand-0.1.0/src/si_deckhand.egg-info/entry_points.txt +2 -0
- si_deckhand-0.1.0/src/si_deckhand.egg-info/requires.txt +7 -0
- si_deckhand-0.1.0/src/si_deckhand.egg-info/top_level.txt +1 -0
- si_deckhand-0.1.0/tests/test_indexer.py +116 -0
- si_deckhand-0.1.0/tests/test_retriever.py +87 -0
- si_deckhand-0.1.0/tests/test_wiki.py +62 -0
|
@@ -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,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())
|