aidos 0.0.1__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.
- aidos-0.0.1/LICENSE +21 -0
- aidos-0.0.1/MANIFEST.in +14 -0
- aidos-0.0.1/PKG-INFO +128 -0
- aidos-0.0.1/README.md +62 -0
- aidos-0.0.1/projectdescription.md +106 -0
- aidos-0.0.1/pyproject.toml +37 -0
- aidos-0.0.1/requirements.txt +4 -0
- aidos-0.0.1/setup.cfg +4 -0
- aidos-0.0.1/src/aidos/__init__.py +27 -0
- aidos-0.0.1/src/aidos/cli.py +168 -0
- aidos-0.0.1/src/aidos/reindex.py +139 -0
- aidos-0.0.1/src/aidos/roadmap_tool.py +225 -0
- aidos-0.0.1/src/aidos.egg-info/PKG-INFO +128 -0
- aidos-0.0.1/src/aidos.egg-info/SOURCES.txt +16 -0
- aidos-0.0.1/src/aidos.egg-info/dependency_links.txt +1 -0
- aidos-0.0.1/src/aidos.egg-info/entry_points.txt +2 -0
- aidos-0.0.1/src/aidos.egg-info/requires.txt +4 -0
- aidos-0.0.1/src/aidos.egg-info/top_level.txt +1 -0
aidos-0.0.1/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Yingding Wang
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
aidos-0.0.1/MANIFEST.in
ADDED
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
include LICENSE
|
|
2
|
+
include projectdescription.md
|
|
3
|
+
include requirements.txt
|
|
4
|
+
|
|
5
|
+
recursive-include src/aidos *.py
|
|
6
|
+
|
|
7
|
+
recursive-exclude src/aidos/graphify-out *
|
|
8
|
+
recursive-exclude src/aidos __pycache__ *.pyc *.pyo
|
|
9
|
+
|
|
10
|
+
prune tests
|
|
11
|
+
prune scripts
|
|
12
|
+
prune docs
|
|
13
|
+
prune config
|
|
14
|
+
prune output
|
aidos-0.0.1/PKG-INFO
ADDED
|
@@ -0,0 +1,128 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: aidos
|
|
3
|
+
Version: 0.0.1
|
|
4
|
+
Summary: aidos (AI Diary Operating System) — the control-plane CLI for an agentic harness: roadmap index, knowledge-graph reindex, and module setup.
|
|
5
|
+
Author: Yingding Wang
|
|
6
|
+
License-Expression: MIT
|
|
7
|
+
Keywords: aidos,ai-diary,harness,control-plane,knowledge-graph,roadmap
|
|
8
|
+
Classifier: Development Status :: 3 - Alpha
|
|
9
|
+
Classifier: Intended Audience :: Developers
|
|
10
|
+
Classifier: Programming Language :: Python :: 3
|
|
11
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
12
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
13
|
+
Classifier: Programming Language :: Python :: 3.14
|
|
14
|
+
Classifier: Topic :: Software Development :: Libraries
|
|
15
|
+
Requires-Python: >=3.12
|
|
16
|
+
Description-Content-Type: text/markdown
|
|
17
|
+
License-File: LICENSE
|
|
18
|
+
Requires-Dist: typer>=0.15.1
|
|
19
|
+
Provides-Extra: mcp
|
|
20
|
+
Requires-Dist: mcp>=1.27.1; extra == "mcp"
|
|
21
|
+
Dynamic: license-file
|
|
22
|
+
|
|
23
|
+
# aidos
|
|
24
|
+
|
|
25
|
+
**AI diary OS** — the control-plane CLI for an agentic harness. Index your roadmaps, keep knowledge graphs fresh, and stand up new modules, all from one command.
|
|
26
|
+
|
|
27
|
+
## What it does
|
|
28
|
+
|
|
29
|
+
`aidos` is the operations layer that sits *above* a multi-module workspace and manages the shared machinery your agent depends on:
|
|
30
|
+
|
|
31
|
+
- **Roadmap index** — parse a roadmap markdown into identified items (stable UUIDs), classify them done / active / deferred, and build a SQLite **FTS5** keyword index for fast search. The markdown stays the source of truth; the index is derived.
|
|
32
|
+
- **Graph reindex** — discover every subproject's knowledge graphs, report freshness, and rebuild them in the correct virtualenv. It never kills your MCP servers — it reports which ones need a restart.
|
|
33
|
+
- **Module setup** — a reproducible recipe (and script) to make a new Python module a first-class citizen: its own venv, its own code + docs graphs, and wiring into the shared portal.
|
|
34
|
+
|
|
35
|
+
It is **CLI-first**: all logic lives behind plain functions, so it is testable and scriptable without a server. A future control **MCP** wraps the same CLI.
|
|
36
|
+
|
|
37
|
+
The name is a nod to [`aidiary`](https://pypi.org/project/aidiary/) (the *AI diary* memory system): `aidos` is the **OS** layer around it.
|
|
38
|
+
|
|
39
|
+
## Install
|
|
40
|
+
|
|
41
|
+
Requires: Python 3.12+
|
|
42
|
+
|
|
43
|
+
```bash
|
|
44
|
+
pip install aidos
|
|
45
|
+
```
|
|
46
|
+
|
|
47
|
+
Building a module's own knowledge graphs additionally uses `aidiary[graphs]` (installed editable in dev):
|
|
48
|
+
|
|
49
|
+
```bash
|
|
50
|
+
pip install "aidiary[graphs]"
|
|
51
|
+
```
|
|
52
|
+
|
|
53
|
+
## Quick start
|
|
54
|
+
|
|
55
|
+
### 1. Check graph freshness across the workspace
|
|
56
|
+
|
|
57
|
+
```bash
|
|
58
|
+
aidos status
|
|
59
|
+
```
|
|
60
|
+
|
|
61
|
+
Lists each subproject's graph layers (code / docs / …) with a ✓ fresh / ⚠ stale / ✗ missing badge based on `graph.json` mtime vs. newest source.
|
|
62
|
+
|
|
63
|
+
### 2. Index a roadmap and search it
|
|
64
|
+
|
|
65
|
+
```bash
|
|
66
|
+
aidos roadmap index path/to/ProjectRoadmap.md
|
|
67
|
+
aidos roadmap search "graph reindex" --db output/aidos/ProjectRoadmap.db
|
|
68
|
+
```
|
|
69
|
+
|
|
70
|
+
`index` assigns a stable UUID to every item and writes a SQLite FTS5 index + a JSON sidecar. `search` is instant keyword retrieval — no embeddings, no LLM.
|
|
71
|
+
|
|
72
|
+
### 3. Preview a done / active split (non-destructive)
|
|
73
|
+
|
|
74
|
+
```bash
|
|
75
|
+
aidos roadmap split path/to/ProjectRoadmap.md
|
|
76
|
+
```
|
|
77
|
+
|
|
78
|
+
Writes `*-archive.md` (done) and `*-active.md` (active + deferred) previews. **The original file is never modified** — you review first.
|
|
79
|
+
|
|
80
|
+
### 4. Rebuild knowledge graphs
|
|
81
|
+
|
|
82
|
+
```bash
|
|
83
|
+
aidos reindex --dry-run # show what would rebuild
|
|
84
|
+
aidos reindex --subproject aidos # rebuild one subproject's code graph
|
|
85
|
+
```
|
|
86
|
+
|
|
87
|
+
Any rebuilt layer is reported as `restart_required` — restart those MCP servers through your editor (or your restart script), never by killing the process.
|
|
88
|
+
|
|
89
|
+
## Directory layout
|
|
90
|
+
|
|
91
|
+
```
|
|
92
|
+
my-module/
|
|
93
|
+
├── pyproject.toml
|
|
94
|
+
├── config/graphs.toml # graphs.code (src/, ast) + graphs.docs (docs/, semantic)
|
|
95
|
+
├── src/<pkg>/ # your package
|
|
96
|
+
├── docs/ # concept docs (feed the docs graph)
|
|
97
|
+
└── output/ # generated artifacts
|
|
98
|
+
├── aidos/ # roadmap index db + json sidecar
|
|
99
|
+
└── graphs/{code,docs}/ # graph.json + graph.html
|
|
100
|
+
```
|
|
101
|
+
|
|
102
|
+
## Command reference
|
|
103
|
+
|
|
104
|
+
| Command | Purpose |
|
|
105
|
+
|---------|---------|
|
|
106
|
+
| `aidos status` | Report knowledge-graph freshness across subprojects |
|
|
107
|
+
| `aidos roadmap index <file>` | Build the SQLite FTS5 search index for a roadmap |
|
|
108
|
+
| `aidos roadmap search <query> --db <db>` | Keyword-search the index |
|
|
109
|
+
| `aidos roadmap split <file>` | Non-destructive done / active split preview |
|
|
110
|
+
| `aidos reindex [--subproject N] [--docs] [--dry-run]` | Rebuild per-subproject graphs |
|
|
111
|
+
|
|
112
|
+
## Design principles
|
|
113
|
+
|
|
114
|
+
- **Markdown / files stay the source of truth** — every index, db, and graph is a *derived* artifact. Producers and consumers connect via files on disk, not imports.
|
|
115
|
+
- **Keyword over embeddings** — on a few-hundred-item corpus, SQLite FTS5 is instant and sufficient; vector search is deferred until the data demands it.
|
|
116
|
+
- **Never kill a stdio MCP server** — reindex reports `restart_required`; it does not respawn processes.
|
|
117
|
+
|
|
118
|
+
## What's new
|
|
119
|
+
|
|
120
|
+
### 0.0.1
|
|
121
|
+
|
|
122
|
+
**First cut — the Developer-Control plane CLI**
|
|
123
|
+
|
|
124
|
+
- **`roadmap`** — `index` (parse → stable UUIDs → done/active/deferred → SQLite FTS5 + JSON sidecar), `search` (FTS5 keyword), `split` (non-destructive done/active preview).
|
|
125
|
+
- **`reindex`** — discover subproject graphs, report freshness, rebuild via each subproject's `aidiary-graphs`; reports `restart_required` (never kills MCP servers).
|
|
126
|
+
- **`status`** — workspace-wide graph freshness.
|
|
127
|
+
- **Reproducible module setup** — idempotent `setup.sh` (venv + editable installs + graphs) and a documented "make a module a first-class harness citizen" guide.
|
|
128
|
+
- **Typer CLI**, stdlib-only core (plus Typer); builds to sdist + wheel.
|
aidos-0.0.1/README.md
ADDED
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
# aidos
|
|
2
|
+
|
|
3
|
+
> **aidos** = **AI diary OS** — the control-plane CLI for an agentic harness. Roadmap index, knowledge-graph reindex, and module setup. CLI-first; a control MCP wraps the CLI.
|
|
4
|
+
|
|
5
|
+
This is the **Developer-Control plane** of the harness control plane (see [ArchitectureReview-HarnessEstate-2026-06-24.md](../ArchitectureReview-HarnessEstate-2026-06-24.md) §5). It consolidates ad-hoc harness administration into one stdlib-only package. Roadmap: [#24](../ProjectRoadmap.md).
|
|
6
|
+
|
|
7
|
+
## 📚 Contents
|
|
8
|
+
|
|
9
|
+
- [📝 Overview](#-overview)
|
|
10
|
+
- [🚀 Get Started](#-get-started)
|
|
11
|
+
- [📦 Commands](#-commands)
|
|
12
|
+
- [🤝 Author](#-author)
|
|
13
|
+
- [📄 License](#-license)
|
|
14
|
+
|
|
15
|
+
## 📝 Overview
|
|
16
|
+
|
|
17
|
+
| Module | Responsibility |
|
|
18
|
+
|--------|----------------|
|
|
19
|
+
| `roadmap_tool` | Parse a roadmap markdown → stable per-item UUIDs → classify done/active/deferred → SQLite FTS5 search index → **non-destructive** done/active split preview |
|
|
20
|
+
| `reindex` | Discover per-subproject knowledge graphs, report freshness, rebuild in each subproject's venv (never kills MCP servers — reports `restart_required`) |
|
|
21
|
+
| `cli` | `aidos <command>` dispatch |
|
|
22
|
+
|
|
23
|
+
**Design rules:** markdown stays the source of truth (git-native); the index is a derived artifact. Keyword (FTS5) search only — embeddings deferred (keyword wins on a few-hundred-item corpus). Never `pkill` a stdio MCP server.
|
|
24
|
+
|
|
25
|
+
## 🚀 Get Started
|
|
26
|
+
|
|
27
|
+
Run without installing:
|
|
28
|
+
|
|
29
|
+
```bash
|
|
30
|
+
python aidos/src/aidos/cli.py status
|
|
31
|
+
```
|
|
32
|
+
|
|
33
|
+
Or install editable for the `aidos` command:
|
|
34
|
+
|
|
35
|
+
```bash
|
|
36
|
+
pip install -e aidos
|
|
37
|
+
aidos status
|
|
38
|
+
```
|
|
39
|
+
|
|
40
|
+
## 📦 Commands
|
|
41
|
+
|
|
42
|
+
```bash
|
|
43
|
+
# Roadmap: index, search, non-destructive split preview
|
|
44
|
+
aidos roadmap index copilot-memory/ProjectRoadmap.md
|
|
45
|
+
aidos roadmap search "graph reindex" --db output/aidos/ProjectRoadmap.db
|
|
46
|
+
aidos roadmap split copilot-memory/ProjectRoadmap.md # writes *-archive.md + *-active.md previews
|
|
47
|
+
|
|
48
|
+
# Graphs: freshness + rebuild
|
|
49
|
+
aidos status
|
|
50
|
+
aidos reindex --dry-run
|
|
51
|
+
aidos reindex --subproject copilot-memory
|
|
52
|
+
```
|
|
53
|
+
|
|
54
|
+
`reindex` reports `restart_required` for any rebuilt layer — restart those MCP servers via VS Code (MCP: Restart) or `./restart-mcps.sh`, never by killing the process.
|
|
55
|
+
|
|
56
|
+
## 🤝 Author
|
|
57
|
+
|
|
58
|
+
**Yingding Wang**
|
|
59
|
+
|
|
60
|
+
## 📄 License
|
|
61
|
+
|
|
62
|
+
[MIT](../LICENSE) — © 2026 Yingding Wang
|
|
@@ -0,0 +1,106 @@
|
|
|
1
|
+
# aidos
|
|
2
|
+
|
|
3
|
+
**AI diary OS** — the control-plane CLI for an agentic harness. Index your roadmaps, keep knowledge graphs fresh, and stand up new modules, all from one command.
|
|
4
|
+
|
|
5
|
+
## What it does
|
|
6
|
+
|
|
7
|
+
`aidos` is the operations layer that sits *above* a multi-module workspace and manages the shared machinery your agent depends on:
|
|
8
|
+
|
|
9
|
+
- **Roadmap index** — parse a roadmap markdown into identified items (stable UUIDs), classify them done / active / deferred, and build a SQLite **FTS5** keyword index for fast search. The markdown stays the source of truth; the index is derived.
|
|
10
|
+
- **Graph reindex** — discover every subproject's knowledge graphs, report freshness, and rebuild them in the correct virtualenv. It never kills your MCP servers — it reports which ones need a restart.
|
|
11
|
+
- **Module setup** — a reproducible recipe (and script) to make a new Python module a first-class citizen: its own venv, its own code + docs graphs, and wiring into the shared portal.
|
|
12
|
+
|
|
13
|
+
It is **CLI-first**: all logic lives behind plain functions, so it is testable and scriptable without a server. A future control **MCP** wraps the same CLI.
|
|
14
|
+
|
|
15
|
+
The name is a nod to [`aidiary`](https://pypi.org/project/aidiary/) (the *AI diary* memory system): `aidos` is the **OS** layer around it.
|
|
16
|
+
|
|
17
|
+
## Install
|
|
18
|
+
|
|
19
|
+
Requires: Python 3.12+
|
|
20
|
+
|
|
21
|
+
```bash
|
|
22
|
+
pip install aidos
|
|
23
|
+
```
|
|
24
|
+
|
|
25
|
+
Building a module's own knowledge graphs additionally uses `aidiary[graphs]` (installed editable in dev):
|
|
26
|
+
|
|
27
|
+
```bash
|
|
28
|
+
pip install "aidiary[graphs]"
|
|
29
|
+
```
|
|
30
|
+
|
|
31
|
+
## Quick start
|
|
32
|
+
|
|
33
|
+
### 1. Check graph freshness across the workspace
|
|
34
|
+
|
|
35
|
+
```bash
|
|
36
|
+
aidos status
|
|
37
|
+
```
|
|
38
|
+
|
|
39
|
+
Lists each subproject's graph layers (code / docs / …) with a ✓ fresh / ⚠ stale / ✗ missing badge based on `graph.json` mtime vs. newest source.
|
|
40
|
+
|
|
41
|
+
### 2. Index a roadmap and search it
|
|
42
|
+
|
|
43
|
+
```bash
|
|
44
|
+
aidos roadmap index path/to/ProjectRoadmap.md
|
|
45
|
+
aidos roadmap search "graph reindex" --db output/aidos/ProjectRoadmap.db
|
|
46
|
+
```
|
|
47
|
+
|
|
48
|
+
`index` assigns a stable UUID to every item and writes a SQLite FTS5 index + a JSON sidecar. `search` is instant keyword retrieval — no embeddings, no LLM.
|
|
49
|
+
|
|
50
|
+
### 3. Preview a done / active split (non-destructive)
|
|
51
|
+
|
|
52
|
+
```bash
|
|
53
|
+
aidos roadmap split path/to/ProjectRoadmap.md
|
|
54
|
+
```
|
|
55
|
+
|
|
56
|
+
Writes `*-archive.md` (done) and `*-active.md` (active + deferred) previews. **The original file is never modified** — you review first.
|
|
57
|
+
|
|
58
|
+
### 4. Rebuild knowledge graphs
|
|
59
|
+
|
|
60
|
+
```bash
|
|
61
|
+
aidos reindex --dry-run # show what would rebuild
|
|
62
|
+
aidos reindex --subproject aidos # rebuild one subproject's code graph
|
|
63
|
+
```
|
|
64
|
+
|
|
65
|
+
Any rebuilt layer is reported as `restart_required` — restart those MCP servers through your editor (or your restart script), never by killing the process.
|
|
66
|
+
|
|
67
|
+
## Directory layout
|
|
68
|
+
|
|
69
|
+
```
|
|
70
|
+
my-module/
|
|
71
|
+
├── pyproject.toml
|
|
72
|
+
├── config/graphs.toml # graphs.code (src/, ast) + graphs.docs (docs/, semantic)
|
|
73
|
+
├── src/<pkg>/ # your package
|
|
74
|
+
├── docs/ # concept docs (feed the docs graph)
|
|
75
|
+
└── output/ # generated artifacts
|
|
76
|
+
├── aidos/ # roadmap index db + json sidecar
|
|
77
|
+
└── graphs/{code,docs}/ # graph.json + graph.html
|
|
78
|
+
```
|
|
79
|
+
|
|
80
|
+
## Command reference
|
|
81
|
+
|
|
82
|
+
| Command | Purpose |
|
|
83
|
+
|---------|---------|
|
|
84
|
+
| `aidos status` | Report knowledge-graph freshness across subprojects |
|
|
85
|
+
| `aidos roadmap index <file>` | Build the SQLite FTS5 search index for a roadmap |
|
|
86
|
+
| `aidos roadmap search <query> --db <db>` | Keyword-search the index |
|
|
87
|
+
| `aidos roadmap split <file>` | Non-destructive done / active split preview |
|
|
88
|
+
| `aidos reindex [--subproject N] [--docs] [--dry-run]` | Rebuild per-subproject graphs |
|
|
89
|
+
|
|
90
|
+
## Design principles
|
|
91
|
+
|
|
92
|
+
- **Markdown / files stay the source of truth** — every index, db, and graph is a *derived* artifact. Producers and consumers connect via files on disk, not imports.
|
|
93
|
+
- **Keyword over embeddings** — on a few-hundred-item corpus, SQLite FTS5 is instant and sufficient; vector search is deferred until the data demands it.
|
|
94
|
+
- **Never kill a stdio MCP server** — reindex reports `restart_required`; it does not respawn processes.
|
|
95
|
+
|
|
96
|
+
## What's new
|
|
97
|
+
|
|
98
|
+
### 0.0.1
|
|
99
|
+
|
|
100
|
+
**First cut — the Developer-Control plane CLI**
|
|
101
|
+
|
|
102
|
+
- **`roadmap`** — `index` (parse → stable UUIDs → done/active/deferred → SQLite FTS5 + JSON sidecar), `search` (FTS5 keyword), `split` (non-destructive done/active preview).
|
|
103
|
+
- **`reindex`** — discover subproject graphs, report freshness, rebuild via each subproject's `aidiary-graphs`; reports `restart_required` (never kills MCP servers).
|
|
104
|
+
- **`status`** — workspace-wide graph freshness.
|
|
105
|
+
- **Reproducible module setup** — idempotent `setup.sh` (venv + editable installs + graphs) and a documented "make a module a first-class harness citizen" guide.
|
|
106
|
+
- **Typer CLI**, stdlib-only core (plus Typer); builds to sdist + wheel.
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
[build-system]
|
|
2
|
+
requires = ["setuptools>=75.0", "wheel"]
|
|
3
|
+
build-backend = "setuptools.build_meta"
|
|
4
|
+
|
|
5
|
+
[project]
|
|
6
|
+
name = "aidos"
|
|
7
|
+
version = "0.0.1"
|
|
8
|
+
description = "aidos (AI Diary Operating System) — the control-plane CLI for an agentic harness: roadmap index, knowledge-graph reindex, and module setup."
|
|
9
|
+
readme = "projectdescription.md"
|
|
10
|
+
requires-python = ">=3.12"
|
|
11
|
+
license = "MIT"
|
|
12
|
+
license-files = ["LICENSE"]
|
|
13
|
+
authors = [{ name = "Yingding Wang" }]
|
|
14
|
+
keywords = ["aidos", "ai-diary", "harness", "control-plane", "knowledge-graph", "roadmap"]
|
|
15
|
+
classifiers = [
|
|
16
|
+
"Development Status :: 3 - Alpha",
|
|
17
|
+
"Intended Audience :: Developers",
|
|
18
|
+
"Programming Language :: Python :: 3",
|
|
19
|
+
"Programming Language :: Python :: 3.12",
|
|
20
|
+
"Programming Language :: Python :: 3.13",
|
|
21
|
+
"Programming Language :: Python :: 3.14",
|
|
22
|
+
"Topic :: Software Development :: Libraries",
|
|
23
|
+
]
|
|
24
|
+
# Base tier is stdlib-only except the CLI framework. Keeps the control plane
|
|
25
|
+
# lightweight and installable everywhere. Version pins live in requirements.txt.
|
|
26
|
+
dependencies = ["typer>=0.15.1"]
|
|
27
|
+
|
|
28
|
+
[project.optional-dependencies]
|
|
29
|
+
# Roadmap #24 Phase 2 — control MCP wraps the CLI.
|
|
30
|
+
mcp = ["mcp>=1.27.1"]
|
|
31
|
+
|
|
32
|
+
[project.scripts]
|
|
33
|
+
aidos = "aidos.cli:main"
|
|
34
|
+
|
|
35
|
+
[tool.setuptools.packages.find]
|
|
36
|
+
where = ["src"]
|
|
37
|
+
include = ["aidos*"]
|
aidos-0.0.1/setup.cfg
ADDED
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
"""aidos (AI diary OS) — workspace control-plane tooling for the harness.
|
|
2
|
+
|
|
3
|
+
This package is the **Developer-Control plane** of the harness control plane
|
|
4
|
+
(see workspace ``ArchitectureReview-HarnessEstate-2026-06-24.md`` §5). It
|
|
5
|
+
consolidates ad-hoc harness administration into a CLI-first, stdlib-only
|
|
6
|
+
package. A future control MCP (Roadmap #24 Phase 2) wraps this same CLI — the
|
|
7
|
+
CLI is the source of truth so the logic is testable without MCP.
|
|
8
|
+
|
|
9
|
+
Exports / submodules:
|
|
10
|
+
|
|
11
|
+
- :mod:`aidos.roadmap_tool` — parse a roadmap markdown into identified
|
|
12
|
+
items (stable UUIDs), classify done/active/deferred, build a SQLite FTS5
|
|
13
|
+
search index, and produce a **non-destructive** done/active split preview.
|
|
14
|
+
- :mod:`aidos.reindex` — discover per-subproject knowledge graphs,
|
|
15
|
+
report freshness/status, and orchestrate rebuilds in each subproject's venv
|
|
16
|
+
(never kills MCP servers — reports ``restart_required`` instead).
|
|
17
|
+
- :mod:`aidos.cli` — argparse dispatch (``aidos <command>``).
|
|
18
|
+
|
|
19
|
+
Consumers: the workspace operator (CLI) today; the ``aidos`` control MCP
|
|
20
|
+
(Phase 2) tomorrow.
|
|
21
|
+
"""
|
|
22
|
+
|
|
23
|
+
from __future__ import annotations
|
|
24
|
+
|
|
25
|
+
__all__ = ["__version__"]
|
|
26
|
+
|
|
27
|
+
__version__ = "0.0.1"
|
|
@@ -0,0 +1,168 @@
|
|
|
1
|
+
"""aidos command-line interface (Typer).
|
|
2
|
+
|
|
3
|
+
Usage::
|
|
4
|
+
|
|
5
|
+
aidos roadmap index <file> [--db PATH]
|
|
6
|
+
aidos roadmap search <query> [--db PATH] [-n N]
|
|
7
|
+
aidos roadmap split <file> [--out-dir DIR] # non-destructive preview
|
|
8
|
+
aidos status # graph freshness
|
|
9
|
+
aidos reindex [--subproject NAME] [--docs] [--dry-run]
|
|
10
|
+
|
|
11
|
+
The CLI is the source of truth; the future control MCP (Roadmap #24 Phase 2)
|
|
12
|
+
wraps these same functions.
|
|
13
|
+
"""
|
|
14
|
+
|
|
15
|
+
from __future__ import annotations
|
|
16
|
+
|
|
17
|
+
from datetime import datetime, timezone
|
|
18
|
+
from pathlib import Path
|
|
19
|
+
from typing import Optional
|
|
20
|
+
|
|
21
|
+
import typer
|
|
22
|
+
|
|
23
|
+
from . import __version__, reindex as _reindex, roadmap_tool as _rt
|
|
24
|
+
|
|
25
|
+
app = typer.Typer(
|
|
26
|
+
no_args_is_help=True,
|
|
27
|
+
add_completion=False,
|
|
28
|
+
help="Workspace control-plane tooling for the harness.",
|
|
29
|
+
)
|
|
30
|
+
roadmap_app = typer.Typer(no_args_is_help=True, help="Parse / index / search / split a roadmap file.")
|
|
31
|
+
app.add_typer(roadmap_app, name="roadmap")
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
def _version_cb(value: bool) -> None:
|
|
35
|
+
if value:
|
|
36
|
+
typer.echo(f"aidos {__version__}")
|
|
37
|
+
raise typer.Exit()
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
@app.callback()
|
|
41
|
+
def _root(
|
|
42
|
+
_version: bool = typer.Option(
|
|
43
|
+
False, "--version", callback=_version_cb, is_eager=True, help="Show version and exit."
|
|
44
|
+
),
|
|
45
|
+
) -> None:
|
|
46
|
+
"""Workspace control-plane tooling for the harness."""
|
|
47
|
+
|
|
48
|
+
|
|
49
|
+
def _find_root(start: Optional[Path] = None) -> Path:
|
|
50
|
+
"""Walk up to the workspace root (marked by .vscode/ or .git/)."""
|
|
51
|
+
here = (start or Path.cwd()).resolve()
|
|
52
|
+
for cand in (here, *here.parents):
|
|
53
|
+
if (cand / ".vscode").is_dir() or (cand / ".git").is_dir():
|
|
54
|
+
return cand
|
|
55
|
+
return here
|
|
56
|
+
|
|
57
|
+
|
|
58
|
+
def _default_db(root: Path, file: Path) -> Path:
|
|
59
|
+
return root / "output" / "aidos" / f"{file.stem}.db"
|
|
60
|
+
|
|
61
|
+
|
|
62
|
+
@roadmap_app.command("index")
|
|
63
|
+
def roadmap_index(
|
|
64
|
+
file: Path = typer.Argument(..., help="Roadmap markdown file to index."),
|
|
65
|
+
db: Optional[Path] = typer.Option(None, help="Output SQLite path (default under output/aidos/)."),
|
|
66
|
+
) -> None:
|
|
67
|
+
"""Parse a roadmap and build the SQLite FTS5 search index."""
|
|
68
|
+
root = _find_root()
|
|
69
|
+
file = file.resolve()
|
|
70
|
+
items = _rt.parse_roadmap(file)
|
|
71
|
+
db_path = db.resolve() if db else _default_db(root, file)
|
|
72
|
+
_rt.build_index(items, db_path)
|
|
73
|
+
counts = _rt.summarize(items)
|
|
74
|
+
typer.echo(f"Indexed {counts['total']} items from {file.name} → {db_path}")
|
|
75
|
+
typer.echo(f" done={counts['done']} active={counts['active']} deferred={counts['deferred']}")
|
|
76
|
+
typer.echo(f" sidecar: {db_path.with_suffix('.json')}")
|
|
77
|
+
|
|
78
|
+
|
|
79
|
+
@roadmap_app.command("search")
|
|
80
|
+
def roadmap_search(
|
|
81
|
+
query: str = typer.Argument(..., help="FTS5 keyword query."),
|
|
82
|
+
db: Optional[Path] = typer.Option(None, help="Index SQLite path to search."),
|
|
83
|
+
n: int = typer.Option(20, "-n", help="Max results."),
|
|
84
|
+
) -> None:
|
|
85
|
+
"""Keyword-search the roadmap index."""
|
|
86
|
+
if db is None or not db.exists():
|
|
87
|
+
typer.echo("error: no index db found; run 'aidos roadmap index <file>' first "
|
|
88
|
+
"or pass --db", err=True)
|
|
89
|
+
raise typer.Exit(2)
|
|
90
|
+
rows = _rt.search_index(db, query, n)
|
|
91
|
+
if not rows:
|
|
92
|
+
typer.echo("(no matches)")
|
|
93
|
+
return
|
|
94
|
+
for r in rows:
|
|
95
|
+
typer.echo(f" [{r['status']:8}] {r['key']:8} {r['title']}")
|
|
96
|
+
|
|
97
|
+
|
|
98
|
+
@roadmap_app.command("split")
|
|
99
|
+
def roadmap_split(
|
|
100
|
+
file: Path = typer.Argument(..., help="Roadmap markdown file to split."),
|
|
101
|
+
out_dir: Optional[Path] = typer.Option(None, help="Preview output dir."),
|
|
102
|
+
) -> None:
|
|
103
|
+
"""Write a NON-DESTRUCTIVE done/active split preview (original untouched)."""
|
|
104
|
+
root = _find_root()
|
|
105
|
+
file = file.resolve()
|
|
106
|
+
items = _rt.parse_roadmap(file)
|
|
107
|
+
target = out_dir.resolve() if out_dir else root / "output" / "aidos" / "split-preview"
|
|
108
|
+
written = _rt.split_preview(file, items, target)
|
|
109
|
+
counts = _rt.summarize(items)
|
|
110
|
+
typer.echo(f"Split preview (non-destructive) for {file.name}:")
|
|
111
|
+
typer.echo(f" archive (done={counts['done']}): {written['archive']}")
|
|
112
|
+
typer.echo(f" active (active+deferred={counts['active'] + counts['deferred']}): {written['active']}")
|
|
113
|
+
typer.echo(" original file was NOT modified.")
|
|
114
|
+
|
|
115
|
+
|
|
116
|
+
@app.command()
|
|
117
|
+
def status() -> None:
|
|
118
|
+
"""Report knowledge-graph freshness across subprojects."""
|
|
119
|
+
root = _find_root()
|
|
120
|
+
rows = _reindex.graph_status(root)
|
|
121
|
+
if not rows:
|
|
122
|
+
typer.echo("(no subprojects with output/graphs/ found)")
|
|
123
|
+
return
|
|
124
|
+
typer.echo(f"Graph status @ {root.name} ({datetime.now(timezone.utc):%Y-%m-%dT%H:%MZ})")
|
|
125
|
+
cur = None
|
|
126
|
+
for ls in rows:
|
|
127
|
+
if ls.subproject != cur:
|
|
128
|
+
cur = ls.subproject
|
|
129
|
+
typer.echo(f"\n{ls.subproject}")
|
|
130
|
+
mark = "✗ missing" if not ls.exists else ("⚠ stale" if ls.stale else "✓ fresh")
|
|
131
|
+
when = (datetime.fromtimestamp(ls.mtime, timezone.utc).strftime("%Y-%m-%d %H:%M")
|
|
132
|
+
if ls.mtime else "—")
|
|
133
|
+
typer.echo(f" {ls.layer:10} {mark:10} graph.json {when}")
|
|
134
|
+
|
|
135
|
+
|
|
136
|
+
@app.command()
|
|
137
|
+
def reindex(
|
|
138
|
+
subproject: Optional[str] = typer.Option(None, help="Only this subproject."),
|
|
139
|
+
docs: bool = typer.Option(False, help="Rebuild docs (heavier) instead of code-only."),
|
|
140
|
+
dry_run: bool = typer.Option(False, "--dry-run", help="Show what would run, don't execute."),
|
|
141
|
+
) -> None:
|
|
142
|
+
"""Rebuild per-subproject knowledge graphs (never restarts MCP servers)."""
|
|
143
|
+
root = _find_root()
|
|
144
|
+
results = _reindex.reindex(root, subproject=subproject, code_only=not docs, dry_run=dry_run)
|
|
145
|
+
if not results:
|
|
146
|
+
typer.echo("(nothing to reindex)")
|
|
147
|
+
return
|
|
148
|
+
failed = False
|
|
149
|
+
for r in results:
|
|
150
|
+
head = "would run" if not r.ran else f"rc={r.returncode}"
|
|
151
|
+
typer.echo(f"{r.subproject}: {head} — {' '.join(r.command)}")
|
|
152
|
+
if r.note:
|
|
153
|
+
typer.echo(f" {r.note}")
|
|
154
|
+
if r.restart_required:
|
|
155
|
+
typer.echo(f" restart_required: {', '.join(r.restart_required)} "
|
|
156
|
+
f"(restart via VS Code MCP or ./restart-mcps.sh — never pkill)")
|
|
157
|
+
if r.ran and r.returncode:
|
|
158
|
+
failed = True
|
|
159
|
+
if failed:
|
|
160
|
+
raise typer.Exit(1)
|
|
161
|
+
|
|
162
|
+
|
|
163
|
+
def main() -> None:
|
|
164
|
+
app()
|
|
165
|
+
|
|
166
|
+
|
|
167
|
+
if __name__ == "__main__":
|
|
168
|
+
main()
|
|
@@ -0,0 +1,139 @@
|
|
|
1
|
+
"""reindex — discover and rebuild per-subproject knowledge graphs.
|
|
2
|
+
|
|
3
|
+
Part of the harness control plane (Roadmap #24 Phase 1). Orchestrates the
|
|
4
|
+
graph-rebuild ritual that is otherwise a manual per-subproject ``cd`` +
|
|
5
|
+
``aidiary-graphs`` dance.
|
|
6
|
+
|
|
7
|
+
Hard constraint: this module **never** kills or restarts MCP servers
|
|
8
|
+
(externally killing a stdio MCP server is a recorded anti-pattern — VS Code
|
|
9
|
+
marks it permanently failed). A rebuild that changes ``graph.json`` reports
|
|
10
|
+
``restart_required`` for the affected layers; the operator (or, later, the
|
|
11
|
+
control MCP) triggers the sanctioned restart via VS Code / ``restart-mcps.sh``.
|
|
12
|
+
"""
|
|
13
|
+
|
|
14
|
+
from __future__ import annotations
|
|
15
|
+
|
|
16
|
+
import subprocess
|
|
17
|
+
from dataclasses import dataclass, field
|
|
18
|
+
from pathlib import Path
|
|
19
|
+
|
|
20
|
+
# Source directory that feeds each graph layer (for staleness comparison).
|
|
21
|
+
_LAYER_SOURCE = {
|
|
22
|
+
"code": ("src",),
|
|
23
|
+
"docs": ("docs",),
|
|
24
|
+
"frontend": ("dashboard/src", "src"),
|
|
25
|
+
"mem": ("memories",),
|
|
26
|
+
}
|
|
27
|
+
_SOURCE_SUFFIXES = {".py", ".ts", ".tsx", ".js", ".jsx", ".md"}
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
@dataclass
|
|
31
|
+
class Subproject:
|
|
32
|
+
name: str
|
|
33
|
+
path: Path
|
|
34
|
+
venv_python: Path
|
|
35
|
+
graphs_dir: Path
|
|
36
|
+
|
|
37
|
+
@property
|
|
38
|
+
def has_graphs(self) -> bool:
|
|
39
|
+
return self.graphs_dir.is_dir()
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
@dataclass
|
|
43
|
+
class LayerStatus:
|
|
44
|
+
subproject: str
|
|
45
|
+
layer: str
|
|
46
|
+
graph_json: Path
|
|
47
|
+
exists: bool
|
|
48
|
+
mtime: float | None
|
|
49
|
+
stale: bool
|
|
50
|
+
newest_source: float | None = None
|
|
51
|
+
|
|
52
|
+
|
|
53
|
+
@dataclass
|
|
54
|
+
class ReindexResult:
|
|
55
|
+
subproject: str
|
|
56
|
+
command: list[str]
|
|
57
|
+
ran: bool
|
|
58
|
+
returncode: int | None = None
|
|
59
|
+
restart_required: list[str] = field(default_factory=list)
|
|
60
|
+
note: str = ""
|
|
61
|
+
|
|
62
|
+
|
|
63
|
+
def discover_subprojects(root: Path) -> list[Subproject]:
|
|
64
|
+
"""Find subprojects with a venv and an ``output/graphs/`` tree."""
|
|
65
|
+
found: list[Subproject] = []
|
|
66
|
+
for child in sorted(p for p in root.iterdir() if p.is_dir()):
|
|
67
|
+
venv_py = child / ".venv" / "bin" / "python"
|
|
68
|
+
graphs = child / "output" / "graphs"
|
|
69
|
+
if venv_py.exists() and graphs.is_dir():
|
|
70
|
+
found.append(Subproject(child.name, child, venv_py, graphs))
|
|
71
|
+
return found
|
|
72
|
+
|
|
73
|
+
|
|
74
|
+
def _newest_source_mtime(base: Path, layer: str) -> float | None:
|
|
75
|
+
newest: float | None = None
|
|
76
|
+
for rel in _LAYER_SOURCE.get(layer, ()):
|
|
77
|
+
src = base / rel
|
|
78
|
+
if not src.is_dir():
|
|
79
|
+
continue
|
|
80
|
+
for f in src.rglob("*"):
|
|
81
|
+
if f.is_file() and f.suffix in _SOURCE_SUFFIXES:
|
|
82
|
+
m = f.stat().st_mtime
|
|
83
|
+
if newest is None or m > newest:
|
|
84
|
+
newest = m
|
|
85
|
+
return newest
|
|
86
|
+
|
|
87
|
+
|
|
88
|
+
def graph_status(root: Path) -> list[LayerStatus]:
|
|
89
|
+
"""Report each (subproject, layer) graph: existence, mtime, staleness."""
|
|
90
|
+
out: list[LayerStatus] = []
|
|
91
|
+
for sp in discover_subprojects(root):
|
|
92
|
+
for layer_dir in sorted(p for p in sp.graphs_dir.iterdir() if p.is_dir()):
|
|
93
|
+
layer = layer_dir.name
|
|
94
|
+
gj = layer_dir / "graph.json"
|
|
95
|
+
# Only report real graph layers (a directory that holds a graph.json).
|
|
96
|
+
# Sibling artifact dirs (analytics/, cache/) and backups are skipped.
|
|
97
|
+
if not gj.exists():
|
|
98
|
+
continue
|
|
99
|
+
mtime = gj.stat().st_mtime
|
|
100
|
+
newest = _newest_source_mtime(sp.path, layer)
|
|
101
|
+
stale = bool(newest is not None and newest > (mtime or 0))
|
|
102
|
+
out.append(LayerStatus(sp.name, layer, gj, True, mtime, stale, newest))
|
|
103
|
+
return out
|
|
104
|
+
|
|
105
|
+
|
|
106
|
+
def reindex(
|
|
107
|
+
root: Path,
|
|
108
|
+
subproject: str | None = None,
|
|
109
|
+
code_only: bool = True,
|
|
110
|
+
dry_run: bool = False,
|
|
111
|
+
) -> list[ReindexResult]:
|
|
112
|
+
"""Rebuild graphs by invoking each subproject's own ``aidiary-graphs``.
|
|
113
|
+
|
|
114
|
+
``code_only`` runs the instant AST rebuild (no LLM). Docs rebuilds are
|
|
115
|
+
heavier and are left to an explicit ``--docs`` run (not yet wired).
|
|
116
|
+
"""
|
|
117
|
+
results: list[ReindexResult] = []
|
|
118
|
+
for sp in discover_subprojects(root):
|
|
119
|
+
if subproject and sp.name != subproject:
|
|
120
|
+
continue
|
|
121
|
+
flag = "--code-only" if code_only else "--build-only"
|
|
122
|
+
cmd = [str(sp.venv_python), "-m", "aidiary.graphs.cli", flag]
|
|
123
|
+
# Layers that would be affected (for restart_required reporting).
|
|
124
|
+
affected = [
|
|
125
|
+
ls.layer for ls in graph_status(root)
|
|
126
|
+
if ls.subproject == sp.name and (not code_only or ls.layer in ("code", "frontend"))
|
|
127
|
+
]
|
|
128
|
+
if dry_run:
|
|
129
|
+
results.append(ReindexResult(sp.name, cmd, ran=False,
|
|
130
|
+
restart_required=affected,
|
|
131
|
+
note="dry-run — would rebuild"))
|
|
132
|
+
continue
|
|
133
|
+
proc = subprocess.run(cmd, cwd=sp.path, capture_output=True, text=True)
|
|
134
|
+
results.append(ReindexResult(
|
|
135
|
+
sp.name, cmd, ran=True, returncode=proc.returncode,
|
|
136
|
+
restart_required=affected if proc.returncode == 0 else [],
|
|
137
|
+
note=(proc.stderr.strip().splitlines() or [""])[-1] if proc.returncode else "ok",
|
|
138
|
+
))
|
|
139
|
+
return results
|
|
@@ -0,0 +1,225 @@
|
|
|
1
|
+
"""roadmap_tool — parse, identify, index, and split roadmap markdown files.
|
|
2
|
+
|
|
3
|
+
Design (Roadmap #24 Phase 1):
|
|
4
|
+
|
|
5
|
+
- **Markdown stays the source of truth** (git-native, hand-editable, PR-reviewable).
|
|
6
|
+
This module derives a fast search index *from* it — it does not replace it.
|
|
7
|
+
- Each roadmap item gets a **stable UUID** (uuid5 over file + item key + title),
|
|
8
|
+
so re-runs are deterministic and an item keeps its id across edits to its body.
|
|
9
|
+
- Status is classified from each item's **own** signals (✅ / strikethrough /
|
|
10
|
+
"Shipped"/"Completed" / "Deferred") — never from a separate priority-matrix
|
|
11
|
+
marker (the matrix is known to lag real status).
|
|
12
|
+
- The done/active **split is a preview** written to a separate directory; the
|
|
13
|
+
original file is never mutated by this tool (operator reviews first).
|
|
14
|
+
|
|
15
|
+
Search uses SQLite **FTS5** (keyword). Embeddings are intentionally NOT used:
|
|
16
|
+
on a few-hundred-item corpus keyword search is instant, and the workspace
|
|
17
|
+
convention is that TF-IDF/keyword beats embeddings on small technical corpora.
|
|
18
|
+
"""
|
|
19
|
+
|
|
20
|
+
from __future__ import annotations
|
|
21
|
+
|
|
22
|
+
import json
|
|
23
|
+
import re
|
|
24
|
+
import sqlite3
|
|
25
|
+
import uuid
|
|
26
|
+
from dataclasses import dataclass, field, asdict
|
|
27
|
+
from pathlib import Path
|
|
28
|
+
|
|
29
|
+
# Stable namespace so UUIDs are reproducible across runs and machines.
|
|
30
|
+
_NS = uuid.uuid5(uuid.NAMESPACE_URL, "aidos/roadmap")
|
|
31
|
+
|
|
32
|
+
# An item heading is a level-2/3 heading whose text starts with an identifier
|
|
33
|
+
# token containing a digit: ``#24``, ``24.``, ``NS-1.1``, ``GLM-1``, ``UI-15``,
|
|
34
|
+
# ``I1``, ``DOCS-2``. Group 1 = hashes, group 2 = optional strikethrough,
|
|
35
|
+
# group 3 = the item key token.
|
|
36
|
+
_ITEM_RE = re.compile(
|
|
37
|
+
r"^(#{2,3})\s+(~~)?\s*(#?[A-Za-z]{0,5}-?\d[\w.\-]*)\b(.*)$"
|
|
38
|
+
)
|
|
39
|
+
|
|
40
|
+
_DONE_RE = re.compile(
|
|
41
|
+
r"(✅|~~|\bShipped\b|\bSHIPPED\b|\bCompleted\b|\bDone\b|\bDONE\b)"
|
|
42
|
+
)
|
|
43
|
+
_DEFER_RE = re.compile(r"defer", re.IGNORECASE)
|
|
44
|
+
|
|
45
|
+
STATUSES = ("done", "deferred", "active")
|
|
46
|
+
|
|
47
|
+
|
|
48
|
+
@dataclass
|
|
49
|
+
class Item:
|
|
50
|
+
"""A single roadmap item parsed from a markdown heading + its body."""
|
|
51
|
+
|
|
52
|
+
uuid: str
|
|
53
|
+
key: str # e.g. "#24", "NS-1.1", "GLM-2", "10"
|
|
54
|
+
title: str
|
|
55
|
+
status: str # one of STATUSES
|
|
56
|
+
level: int # heading level (2 or 3)
|
|
57
|
+
line_start: int # 1-based line of the heading
|
|
58
|
+
line_end: int # 1-based last line of the body (exclusive of next item)
|
|
59
|
+
tags: list[str] = field(default_factory=list)
|
|
60
|
+
body: str = ""
|
|
61
|
+
|
|
62
|
+
def metadata_block(self) -> str:
|
|
63
|
+
"""Human-readable + machine-parseable block to stamp under a heading."""
|
|
64
|
+
return (
|
|
65
|
+
f"- **id:** `{self.uuid}`\n"
|
|
66
|
+
f"- **status:** {self.status}\n"
|
|
67
|
+
f"- **tags:** {', '.join(self.tags) or '—'}\n"
|
|
68
|
+
)
|
|
69
|
+
|
|
70
|
+
|
|
71
|
+
def _clean_title(key: str, trailing: str) -> str:
|
|
72
|
+
text = trailing.strip()
|
|
73
|
+
# Strip leading separators after the key (". ", ") ", "— ", "- ").
|
|
74
|
+
text = re.sub(r"^[.)\s—–-]+", "", text)
|
|
75
|
+
text = text.replace("~~", "").replace("✅", "").strip()
|
|
76
|
+
# Collapse a key that was split across the regex (e.g. "#24" + ". Title").
|
|
77
|
+
return text or key
|
|
78
|
+
|
|
79
|
+
|
|
80
|
+
def _classify(heading_line: str, body: str) -> str:
|
|
81
|
+
if _DEFER_RE.search(heading_line):
|
|
82
|
+
return "deferred"
|
|
83
|
+
if _DONE_RE.search(heading_line):
|
|
84
|
+
return "done"
|
|
85
|
+
# Only look at the first ~25 lines of the body for a status declaration,
|
|
86
|
+
# so a passing mention of "shipped" deep in prose does not mislabel.
|
|
87
|
+
head = "\n".join(body.splitlines()[:25])
|
|
88
|
+
if re.search(r"(\*\*✅|✅\s*Shipped|Status:.*?(Shipped|Completed|Done|✅))", head):
|
|
89
|
+
return "done"
|
|
90
|
+
if _DEFER_RE.search(head):
|
|
91
|
+
return "deferred"
|
|
92
|
+
return "active"
|
|
93
|
+
|
|
94
|
+
|
|
95
|
+
def _tags_for(key: str) -> list[str]:
|
|
96
|
+
m = re.match(r"#?([A-Za-z]+)", key)
|
|
97
|
+
return [m.group(1).upper()] if m and m.group(1) else []
|
|
98
|
+
|
|
99
|
+
|
|
100
|
+
def parse_roadmap(path: Path) -> list[Item]:
|
|
101
|
+
"""Parse a roadmap markdown file into a list of :class:`Item`."""
|
|
102
|
+
lines = path.read_text(encoding="utf-8").splitlines()
|
|
103
|
+
# Locate every item heading first, then slice bodies between them.
|
|
104
|
+
heads: list[tuple[int, int, str, str, str]] = [] # (idx, level, key, hashes, trailing)
|
|
105
|
+
for i, line in enumerate(lines):
|
|
106
|
+
m = _ITEM_RE.match(line)
|
|
107
|
+
if not m:
|
|
108
|
+
continue
|
|
109
|
+
hashes, _strike, key, trailing = m.groups()
|
|
110
|
+
heads.append((i, len(hashes), key, line, trailing))
|
|
111
|
+
|
|
112
|
+
items: list[Item] = []
|
|
113
|
+
for n, (idx, level, key, heading_line, trailing) in enumerate(heads):
|
|
114
|
+
end = heads[n + 1][0] if n + 1 < len(heads) else len(lines)
|
|
115
|
+
body = "\n".join(lines[idx + 1:end]).strip()
|
|
116
|
+
title = _clean_title(key, trailing)
|
|
117
|
+
status = _classify(heading_line, body)
|
|
118
|
+
item_uuid = str(uuid.uuid5(_NS, f"{path.name}:{key}:{title}"))
|
|
119
|
+
items.append(
|
|
120
|
+
Item(
|
|
121
|
+
uuid=item_uuid,
|
|
122
|
+
key=key.lstrip("#") and key, # keep the literal token (e.g. "#24")
|
|
123
|
+
title=title,
|
|
124
|
+
status=status,
|
|
125
|
+
level=level,
|
|
126
|
+
line_start=idx + 1,
|
|
127
|
+
line_end=end,
|
|
128
|
+
tags=_tags_for(key),
|
|
129
|
+
body=body,
|
|
130
|
+
)
|
|
131
|
+
)
|
|
132
|
+
return items
|
|
133
|
+
|
|
134
|
+
|
|
135
|
+
def build_index(items: list[Item], db_path: Path) -> Path:
|
|
136
|
+
"""Write a SQLite FTS5 keyword index + a JSON sidecar. Returns db_path."""
|
|
137
|
+
db_path.parent.mkdir(parents=True, exist_ok=True)
|
|
138
|
+
if db_path.exists():
|
|
139
|
+
db_path.unlink()
|
|
140
|
+
con = sqlite3.connect(db_path)
|
|
141
|
+
try:
|
|
142
|
+
con.execute(
|
|
143
|
+
"CREATE TABLE items (uuid TEXT PRIMARY KEY, key TEXT, title TEXT, "
|
|
144
|
+
"status TEXT, level INT, line_start INT, line_end INT, tags TEXT)"
|
|
145
|
+
)
|
|
146
|
+
con.execute(
|
|
147
|
+
"CREATE VIRTUAL TABLE roadmap_fts USING fts5("
|
|
148
|
+
"uuid UNINDEXED, key, title, status UNINDEXED, tags, body)"
|
|
149
|
+
)
|
|
150
|
+
for it in items:
|
|
151
|
+
con.execute(
|
|
152
|
+
"INSERT INTO items VALUES (?,?,?,?,?,?,?,?)",
|
|
153
|
+
(it.uuid, it.key, it.title, it.status, it.level,
|
|
154
|
+
it.line_start, it.line_end, ",".join(it.tags)),
|
|
155
|
+
)
|
|
156
|
+
con.execute(
|
|
157
|
+
"INSERT INTO roadmap_fts VALUES (?,?,?,?,?,?)",
|
|
158
|
+
(it.uuid, it.key, it.title, it.status, " ".join(it.tags), it.body),
|
|
159
|
+
)
|
|
160
|
+
con.commit()
|
|
161
|
+
finally:
|
|
162
|
+
con.close()
|
|
163
|
+
# JSON sidecar (bodies omitted to keep it small + diff-friendly).
|
|
164
|
+
sidecar = db_path.with_suffix(".json")
|
|
165
|
+
sidecar.write_text(
|
|
166
|
+
json.dumps(
|
|
167
|
+
[{k: v for k, v in asdict(it).items() if k != "body"} for it in items],
|
|
168
|
+
indent=2,
|
|
169
|
+
),
|
|
170
|
+
encoding="utf-8",
|
|
171
|
+
)
|
|
172
|
+
return db_path
|
|
173
|
+
|
|
174
|
+
|
|
175
|
+
def search_index(db_path: Path, query: str, limit: int = 20) -> list[dict]:
|
|
176
|
+
"""Run an FTS5 keyword search; return ranked item rows (no bodies)."""
|
|
177
|
+
con = sqlite3.connect(db_path)
|
|
178
|
+
try:
|
|
179
|
+
con.row_factory = sqlite3.Row
|
|
180
|
+
rows = con.execute(
|
|
181
|
+
"SELECT uuid, key, title, status FROM roadmap_fts "
|
|
182
|
+
"WHERE roadmap_fts MATCH ? ORDER BY rank LIMIT ?",
|
|
183
|
+
(query, limit),
|
|
184
|
+
).fetchall()
|
|
185
|
+
return [dict(r) for r in rows]
|
|
186
|
+
finally:
|
|
187
|
+
con.close()
|
|
188
|
+
|
|
189
|
+
|
|
190
|
+
def split_preview(path: Path, items: list[Item], out_dir: Path) -> dict[str, Path]:
|
|
191
|
+
"""Write NON-DESTRUCTIVE done/active+deferred split previews.
|
|
192
|
+
|
|
193
|
+
The original ``path`` is never modified. Returns the two written paths.
|
|
194
|
+
"""
|
|
195
|
+
out_dir.mkdir(parents=True, exist_ok=True)
|
|
196
|
+
stem = path.stem
|
|
197
|
+
archive = out_dir / f"{stem}-archive.md"
|
|
198
|
+
active = out_dir / f"{stem}-active.md"
|
|
199
|
+
|
|
200
|
+
def _render(title: str, chosen: list[Item]) -> str:
|
|
201
|
+
out = [f"# {title}", "", f"> Generated by aidos from {path.name} "
|
|
202
|
+
f"(non-destructive preview). {len(chosen)} items.", ""]
|
|
203
|
+
for it in chosen:
|
|
204
|
+
out.append(f"## {it.key} — {it.title}")
|
|
205
|
+
out.append("")
|
|
206
|
+
out.append(it.metadata_block())
|
|
207
|
+
out.append(it.body)
|
|
208
|
+
out.append("")
|
|
209
|
+
out.append("---")
|
|
210
|
+
out.append("")
|
|
211
|
+
return "\n".join(out)
|
|
212
|
+
|
|
213
|
+
done = [it for it in items if it.status == "done"]
|
|
214
|
+
rest = [it for it in items if it.status != "done"]
|
|
215
|
+
archive.write_text(_render(f"{stem} — Archive (done)", done), encoding="utf-8")
|
|
216
|
+
active.write_text(_render(f"{stem} — Active / Deferred", rest), encoding="utf-8")
|
|
217
|
+
return {"archive": archive, "active": active}
|
|
218
|
+
|
|
219
|
+
|
|
220
|
+
def summarize(items: list[Item]) -> dict[str, int]:
|
|
221
|
+
counts = {s: 0 for s in STATUSES}
|
|
222
|
+
for it in items:
|
|
223
|
+
counts[it.status] = counts.get(it.status, 0) + 1
|
|
224
|
+
counts["total"] = len(items)
|
|
225
|
+
return counts
|
|
@@ -0,0 +1,128 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: aidos
|
|
3
|
+
Version: 0.0.1
|
|
4
|
+
Summary: aidos (AI Diary Operating System) — the control-plane CLI for an agentic harness: roadmap index, knowledge-graph reindex, and module setup.
|
|
5
|
+
Author: Yingding Wang
|
|
6
|
+
License-Expression: MIT
|
|
7
|
+
Keywords: aidos,ai-diary,harness,control-plane,knowledge-graph,roadmap
|
|
8
|
+
Classifier: Development Status :: 3 - Alpha
|
|
9
|
+
Classifier: Intended Audience :: Developers
|
|
10
|
+
Classifier: Programming Language :: Python :: 3
|
|
11
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
12
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
13
|
+
Classifier: Programming Language :: Python :: 3.14
|
|
14
|
+
Classifier: Topic :: Software Development :: Libraries
|
|
15
|
+
Requires-Python: >=3.12
|
|
16
|
+
Description-Content-Type: text/markdown
|
|
17
|
+
License-File: LICENSE
|
|
18
|
+
Requires-Dist: typer>=0.15.1
|
|
19
|
+
Provides-Extra: mcp
|
|
20
|
+
Requires-Dist: mcp>=1.27.1; extra == "mcp"
|
|
21
|
+
Dynamic: license-file
|
|
22
|
+
|
|
23
|
+
# aidos
|
|
24
|
+
|
|
25
|
+
**AI diary OS** — the control-plane CLI for an agentic harness. Index your roadmaps, keep knowledge graphs fresh, and stand up new modules, all from one command.
|
|
26
|
+
|
|
27
|
+
## What it does
|
|
28
|
+
|
|
29
|
+
`aidos` is the operations layer that sits *above* a multi-module workspace and manages the shared machinery your agent depends on:
|
|
30
|
+
|
|
31
|
+
- **Roadmap index** — parse a roadmap markdown into identified items (stable UUIDs), classify them done / active / deferred, and build a SQLite **FTS5** keyword index for fast search. The markdown stays the source of truth; the index is derived.
|
|
32
|
+
- **Graph reindex** — discover every subproject's knowledge graphs, report freshness, and rebuild them in the correct virtualenv. It never kills your MCP servers — it reports which ones need a restart.
|
|
33
|
+
- **Module setup** — a reproducible recipe (and script) to make a new Python module a first-class citizen: its own venv, its own code + docs graphs, and wiring into the shared portal.
|
|
34
|
+
|
|
35
|
+
It is **CLI-first**: all logic lives behind plain functions, so it is testable and scriptable without a server. A future control **MCP** wraps the same CLI.
|
|
36
|
+
|
|
37
|
+
The name is a nod to [`aidiary`](https://pypi.org/project/aidiary/) (the *AI diary* memory system): `aidos` is the **OS** layer around it.
|
|
38
|
+
|
|
39
|
+
## Install
|
|
40
|
+
|
|
41
|
+
Requires: Python 3.12+
|
|
42
|
+
|
|
43
|
+
```bash
|
|
44
|
+
pip install aidos
|
|
45
|
+
```
|
|
46
|
+
|
|
47
|
+
Building a module's own knowledge graphs additionally uses `aidiary[graphs]` (installed editable in dev):
|
|
48
|
+
|
|
49
|
+
```bash
|
|
50
|
+
pip install "aidiary[graphs]"
|
|
51
|
+
```
|
|
52
|
+
|
|
53
|
+
## Quick start
|
|
54
|
+
|
|
55
|
+
### 1. Check graph freshness across the workspace
|
|
56
|
+
|
|
57
|
+
```bash
|
|
58
|
+
aidos status
|
|
59
|
+
```
|
|
60
|
+
|
|
61
|
+
Lists each subproject's graph layers (code / docs / …) with a ✓ fresh / ⚠ stale / ✗ missing badge based on `graph.json` mtime vs. newest source.
|
|
62
|
+
|
|
63
|
+
### 2. Index a roadmap and search it
|
|
64
|
+
|
|
65
|
+
```bash
|
|
66
|
+
aidos roadmap index path/to/ProjectRoadmap.md
|
|
67
|
+
aidos roadmap search "graph reindex" --db output/aidos/ProjectRoadmap.db
|
|
68
|
+
```
|
|
69
|
+
|
|
70
|
+
`index` assigns a stable UUID to every item and writes a SQLite FTS5 index + a JSON sidecar. `search` is instant keyword retrieval — no embeddings, no LLM.
|
|
71
|
+
|
|
72
|
+
### 3. Preview a done / active split (non-destructive)
|
|
73
|
+
|
|
74
|
+
```bash
|
|
75
|
+
aidos roadmap split path/to/ProjectRoadmap.md
|
|
76
|
+
```
|
|
77
|
+
|
|
78
|
+
Writes `*-archive.md` (done) and `*-active.md` (active + deferred) previews. **The original file is never modified** — you review first.
|
|
79
|
+
|
|
80
|
+
### 4. Rebuild knowledge graphs
|
|
81
|
+
|
|
82
|
+
```bash
|
|
83
|
+
aidos reindex --dry-run # show what would rebuild
|
|
84
|
+
aidos reindex --subproject aidos # rebuild one subproject's code graph
|
|
85
|
+
```
|
|
86
|
+
|
|
87
|
+
Any rebuilt layer is reported as `restart_required` — restart those MCP servers through your editor (or your restart script), never by killing the process.
|
|
88
|
+
|
|
89
|
+
## Directory layout
|
|
90
|
+
|
|
91
|
+
```
|
|
92
|
+
my-module/
|
|
93
|
+
├── pyproject.toml
|
|
94
|
+
├── config/graphs.toml # graphs.code (src/, ast) + graphs.docs (docs/, semantic)
|
|
95
|
+
├── src/<pkg>/ # your package
|
|
96
|
+
├── docs/ # concept docs (feed the docs graph)
|
|
97
|
+
└── output/ # generated artifacts
|
|
98
|
+
├── aidos/ # roadmap index db + json sidecar
|
|
99
|
+
└── graphs/{code,docs}/ # graph.json + graph.html
|
|
100
|
+
```
|
|
101
|
+
|
|
102
|
+
## Command reference
|
|
103
|
+
|
|
104
|
+
| Command | Purpose |
|
|
105
|
+
|---------|---------|
|
|
106
|
+
| `aidos status` | Report knowledge-graph freshness across subprojects |
|
|
107
|
+
| `aidos roadmap index <file>` | Build the SQLite FTS5 search index for a roadmap |
|
|
108
|
+
| `aidos roadmap search <query> --db <db>` | Keyword-search the index |
|
|
109
|
+
| `aidos roadmap split <file>` | Non-destructive done / active split preview |
|
|
110
|
+
| `aidos reindex [--subproject N] [--docs] [--dry-run]` | Rebuild per-subproject graphs |
|
|
111
|
+
|
|
112
|
+
## Design principles
|
|
113
|
+
|
|
114
|
+
- **Markdown / files stay the source of truth** — every index, db, and graph is a *derived* artifact. Producers and consumers connect via files on disk, not imports.
|
|
115
|
+
- **Keyword over embeddings** — on a few-hundred-item corpus, SQLite FTS5 is instant and sufficient; vector search is deferred until the data demands it.
|
|
116
|
+
- **Never kill a stdio MCP server** — reindex reports `restart_required`; it does not respawn processes.
|
|
117
|
+
|
|
118
|
+
## What's new
|
|
119
|
+
|
|
120
|
+
### 0.0.1
|
|
121
|
+
|
|
122
|
+
**First cut — the Developer-Control plane CLI**
|
|
123
|
+
|
|
124
|
+
- **`roadmap`** — `index` (parse → stable UUIDs → done/active/deferred → SQLite FTS5 + JSON sidecar), `search` (FTS5 keyword), `split` (non-destructive done/active preview).
|
|
125
|
+
- **`reindex`** — discover subproject graphs, report freshness, rebuild via each subproject's `aidiary-graphs`; reports `restart_required` (never kills MCP servers).
|
|
126
|
+
- **`status`** — workspace-wide graph freshness.
|
|
127
|
+
- **Reproducible module setup** — idempotent `setup.sh` (venv + editable installs + graphs) and a documented "make a module a first-class harness citizen" guide.
|
|
128
|
+
- **Typer CLI**, stdlib-only core (plus Typer); builds to sdist + wheel.
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
LICENSE
|
|
2
|
+
MANIFEST.in
|
|
3
|
+
README.md
|
|
4
|
+
projectdescription.md
|
|
5
|
+
pyproject.toml
|
|
6
|
+
requirements.txt
|
|
7
|
+
src/aidos/__init__.py
|
|
8
|
+
src/aidos/cli.py
|
|
9
|
+
src/aidos/reindex.py
|
|
10
|
+
src/aidos/roadmap_tool.py
|
|
11
|
+
src/aidos.egg-info/PKG-INFO
|
|
12
|
+
src/aidos.egg-info/SOURCES.txt
|
|
13
|
+
src/aidos.egg-info/dependency_links.txt
|
|
14
|
+
src/aidos.egg-info/entry_points.txt
|
|
15
|
+
src/aidos.egg-info/requires.txt
|
|
16
|
+
src/aidos.egg-info/top_level.txt
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
aidos
|