@koffeekat/diana 0.0.0-stage → 0.1.0-beta.1
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.
- package/.claude-plugin/marketplace.json +13 -0
- package/.claude-plugin/plugin.json +9 -0
- package/LICENSE.md +21 -0
- package/README.md +165 -2
- package/dist/cli.d.ts +2 -0
- package/dist/cli.js +75 -0
- package/dist/cli.js.map +1 -0
- package/dist/commands/index.d.ts +4 -0
- package/dist/commands/index.js +19 -0
- package/dist/commands/index.js.map +1 -0
- package/dist/commands/init.d.ts +4 -0
- package/dist/commands/init.js +36 -0
- package/dist/commands/init.js.map +1 -0
- package/dist/commands/read.d.ts +13 -0
- package/dist/commands/read.js +74 -0
- package/dist/commands/read.js.map +1 -0
- package/dist/commands/search.d.ts +13 -0
- package/dist/commands/search.js +52 -0
- package/dist/commands/search.js.map +1 -0
- package/dist/indexer.d.ts +16 -0
- package/dist/indexer.js +258 -0
- package/dist/indexer.js.map +1 -0
- package/dist/sources.d.ts +11 -0
- package/dist/sources.js +85 -0
- package/dist/sources.js.map +1 -0
- package/dist/templates/config.template.d.ts +2 -0
- package/dist/templates/config.template.js +5 -0
- package/dist/templates/config.template.js.map +1 -0
- package/dist/templates/gitignore.template.d.ts +1 -0
- package/dist/templates/gitignore.template.js +5 -0
- package/dist/templates/gitignore.template.js.map +1 -0
- package/dist/types/source.types.d.ts +4 -0
- package/dist/types/source.types.js +2 -0
- package/dist/types/source.types.js.map +1 -0
- package/dist/types/workspace.types.d.ts +27 -0
- package/dist/types/workspace.types.js +12 -0
- package/dist/types/workspace.types.js.map +1 -0
- package/dist/utils/forbiddenDirs.d.ts +1 -0
- package/dist/utils/forbiddenDirs.js +2 -0
- package/dist/utils/forbiddenDirs.js.map +1 -0
- package/dist/utils/hasErrorCode.d.ts +1 -0
- package/dist/utils/hasErrorCode.js +4 -0
- package/dist/utils/hasErrorCode.js.map +1 -0
- package/dist/utils/pathExists.d.ts +1 -0
- package/dist/utils/pathExists.js +15 -0
- package/dist/utils/pathExists.js.map +1 -0
- package/dist/workspace.d.ts +14 -0
- package/dist/workspace.js +70 -0
- package/dist/workspace.js.map +1 -0
- package/docs/security.md +32 -0
- package/img/diana-banner.webp +0 -0
- package/package.json +61 -4
- package/skills/diana/SKILL.md +23 -0
package/LICENSE.md
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 KoffeeKat
|
|
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.
|
package/README.md
CHANGED
|
@@ -1,3 +1,166 @@
|
|
|
1
|
-
|
|
1
|
+
<div>
|
|
2
|
+
<img alt="diana-banner" src="./img/diana-banner.webp" />
|
|
3
|
+
</div>
|
|
2
4
|
|
|
3
|
-
|
|
5
|
+
<div align="center">
|
|
6
|
+
<p> </p>
|
|
7
|
+
<h1 align="center">diana</h1>
|
|
8
|
+
<h4 align="center">A lightweight local Markdown search layer for agents</h4>
|
|
9
|
+
<p> </p>
|
|
10
|
+
</div>
|
|
11
|
+
|
|
12
|
+
### Search for relevant sections and read only what you need.
|
|
13
|
+
|
|
14
|
+
Backed by SQLite FTS5 and a selective indexing engine, Diana helps agents navigate large document collections with precision. It aims to reduce token usage and keep the context window uncluttered.
|
|
15
|
+
|
|
16
|
+
<p> </p>
|
|
17
|
+
|
|
18
|
+
## Installation
|
|
19
|
+
|
|
20
|
+
Requires **Node.js >=22.13.0**.
|
|
21
|
+
|
|
22
|
+
```sh
|
|
23
|
+
npm install -g @koffeekat/diana@beta
|
|
24
|
+
```
|
|
25
|
+
|
|
26
|
+
<p> </p>
|
|
27
|
+
|
|
28
|
+
## Quick start
|
|
29
|
+
|
|
30
|
+
From your project root:
|
|
31
|
+
|
|
32
|
+
```sh
|
|
33
|
+
diana init
|
|
34
|
+
```
|
|
35
|
+
|
|
36
|
+
[Add a source](#adding-sources), then index it:
|
|
37
|
+
|
|
38
|
+
```sh
|
|
39
|
+
diana index
|
|
40
|
+
```
|
|
41
|
+
|
|
42
|
+
Run Diana from this same root. `diana index` refreshes every configured source.
|
|
43
|
+
|
|
44
|
+
> Quick hint: consider adding `diana index` as a pre-commit hook.
|
|
45
|
+
|
|
46
|
+
<p> </p>
|
|
47
|
+
|
|
48
|
+
## Other CLI commands
|
|
49
|
+
|
|
50
|
+
| Command | What it does |
|
|
51
|
+
| ----------------------------------------------------------- | ---------------------------------------------------------- |
|
|
52
|
+
| `diana search "token rotation"` | Find matching sections. Words are ANDed; results are JSON. |
|
|
53
|
+
| `diana read notes/architecture.md` | Read a note with limited output, returned as JSON. |
|
|
54
|
+
| `diana read notes/architecture.md --section token-rotation` | Read one section using its search-result anchor. |
|
|
55
|
+
| `diana --help` | Show available commands. |
|
|
56
|
+
|
|
57
|
+
Search-result `path` values are relative to `sourceRoot`; join both for an absolute read path. If a read returns `truncated: true`, request a narrower section where possible.
|
|
58
|
+
|
|
59
|
+
<p> </p>
|
|
60
|
+
|
|
61
|
+
## Adding sources
|
|
62
|
+
|
|
63
|
+
Edit `diana.config.json` to share project sources:
|
|
64
|
+
|
|
65
|
+
```json
|
|
66
|
+
{
|
|
67
|
+
"version": 1,
|
|
68
|
+
"sources": [
|
|
69
|
+
{
|
|
70
|
+
"path": "notes",
|
|
71
|
+
"include": ["**/*.md"],
|
|
72
|
+
"exclude": ["drafts/**"]
|
|
73
|
+
}
|
|
74
|
+
]
|
|
75
|
+
}
|
|
76
|
+
```
|
|
77
|
+
|
|
78
|
+
> The directory must already exist. Exclude patterns take priority. Use `exclude: []` when nothing should be excluded.
|
|
79
|
+
|
|
80
|
+
- **Project config — `diana.config.json`:** commit it so teammates share the same setup. Sources must stay inside the project.
|
|
81
|
+
- **Local config — `.diana/local.config.json`:** same format, for personal sources, including directories outside the project. Use an absolute path for an external source directory; `~` isn't expanded. Relative paths in either config start at the project root.
|
|
82
|
+
|
|
83
|
+
For example, `.diana/local.config.json` can point to a personal source directory:
|
|
84
|
+
|
|
85
|
+
```json
|
|
86
|
+
{
|
|
87
|
+
"version": 1,
|
|
88
|
+
"sources": [
|
|
89
|
+
{
|
|
90
|
+
"path": "/Users/you/Documents/personal-notes",
|
|
91
|
+
"include": ["**/*.md"],
|
|
92
|
+
"exclude": [],
|
|
93
|
+
"autoReindex": true
|
|
94
|
+
}
|
|
95
|
+
]
|
|
96
|
+
}
|
|
97
|
+
```
|
|
98
|
+
|
|
99
|
+
Replace the path with your source directory's absolute path, then run `diana index` once.
|
|
100
|
+
|
|
101
|
+
- `autoReindex` - refresh this source before each search. Useful for sources updated independently of your project. Defaults to false.
|
|
102
|
+
|
|
103
|
+
> 💡 Prefer pre-commit/post-merge hooks when commit-level freshness is enough. Use autoReindex when searches should also pick up uncommitted changes; it reads and hashes the source’s included files on every search.
|
|
104
|
+
|
|
105
|
+
**Keep the generated `.diana/.gitignore`.** It excludes your local config and index, helping prevent personal paths and cached note content from being committed. Git ignore rules aren't access controls; review both configs in unfamiliar projects. See [security and privacy](docs/security.md).
|
|
106
|
+
|
|
107
|
+
<p> </p>
|
|
108
|
+
|
|
109
|
+
## Installing skills
|
|
110
|
+
|
|
111
|
+
Skills teach agents to search first and read only relevant sections. Install the CLI above first; your harness manages skill installation.
|
|
112
|
+
|
|
113
|
+
**Pi**
|
|
114
|
+
|
|
115
|
+
```sh
|
|
116
|
+
pi install npm:@koffeekat/diana@beta
|
|
117
|
+
```
|
|
118
|
+
|
|
119
|
+
Reload with `/reload` if needed; invoke `/skill:diana`.
|
|
120
|
+
|
|
121
|
+
**Claude Code**
|
|
122
|
+
|
|
123
|
+
```text
|
|
124
|
+
/plugin marketplace add K0ffeeKat/diana
|
|
125
|
+
/plugin install diana@koffeeapps-diana
|
|
126
|
+
```
|
|
127
|
+
|
|
128
|
+
Invoke `/diana:diana`.
|
|
129
|
+
|
|
130
|
+
**Codex** — ask `$skill-installer`:
|
|
131
|
+
|
|
132
|
+
```text
|
|
133
|
+
Install skills/diana from the GitHub repository K0ffeeKat/diana.
|
|
134
|
+
```
|
|
135
|
+
|
|
136
|
+
Invoke `$diana` once available.
|
|
137
|
+
|
|
138
|
+
<p> </p>
|
|
139
|
+
|
|
140
|
+
## Using Diana with Obsidian
|
|
141
|
+
|
|
142
|
+
Your vault can live inside the project:
|
|
143
|
+
|
|
144
|
+
```text
|
|
145
|
+
project/
|
|
146
|
+
├── diana.config.json # source path: "notes"
|
|
147
|
+
├── .diana/ # local config and index
|
|
148
|
+
└── notes/ # open this folder as an Obsidian vault
|
|
149
|
+
├── .obsidian/ # skipped by Diana
|
|
150
|
+
└── architecture.md
|
|
151
|
+
```
|
|
152
|
+
|
|
153
|
+
Keep editing in Obsidian as usual. Diana indexes the Markdown, skips hidden entries and symlinks, and leaves the vault unchanged. An external vault works through the local config instead.
|
|
154
|
+
|
|
155
|
+
<p> </p>
|
|
156
|
+
|
|
157
|
+
## Troubleshooting
|
|
158
|
+
|
|
159
|
+
- **Command not found:** check the npm installation and your PATH. Installing a skill alone doesn't install the CLI.
|
|
160
|
+
- **No configuration:** run from the project root, where you ran `diana init`.
|
|
161
|
+
- **No sources/index, outdated schema, or stale results:** add a source if needed, check its patterns, and run `diana index`. Automatic refresh still needs an initial, compatible index.
|
|
162
|
+
- **Source outside project:** put it in the local config, not the project config.
|
|
163
|
+
- **Section not found:** reindex for current anchors; omit `--section` for headingless notes or pre-heading text.
|
|
164
|
+
- **File too large:** split notes larger than 1 MiB; requesting a section doesn't bypass this limit.
|
|
165
|
+
|
|
166
|
+
Removed sources disappear from cached results on the next successful search, even if no sources remain. To discard the cache entirely, stop Diana commands and delete only `.diana/index.sqlite`; preserve your local config.
|
package/dist/cli.d.ts
ADDED
package/dist/cli.js
ADDED
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import { initializeWorkspace } from "./commands/init.js";
|
|
3
|
+
import { indexWorkspace } from "./commands/index.js";
|
|
4
|
+
import { readWorkspace } from "./commands/read.js";
|
|
5
|
+
import { searchWorkspace } from "./commands/search.js";
|
|
6
|
+
const [command, ...arguments_] = process.argv.slice(2);
|
|
7
|
+
const USAGE_HINT = `Usage:
|
|
8
|
+
diana <command>
|
|
9
|
+
|
|
10
|
+
Commands:
|
|
11
|
+
init
|
|
12
|
+
index
|
|
13
|
+
search <query...>
|
|
14
|
+
read <path> [--section <anchor>]
|
|
15
|
+
`;
|
|
16
|
+
try {
|
|
17
|
+
if ((command === "init" ||
|
|
18
|
+
command === "index" ||
|
|
19
|
+
command === "--help" ||
|
|
20
|
+
command === "-h") &&
|
|
21
|
+
arguments_.length > 0) {
|
|
22
|
+
throw new Error(`Usage: diana ${command}.`);
|
|
23
|
+
}
|
|
24
|
+
switch (command) {
|
|
25
|
+
case undefined:
|
|
26
|
+
case "--help":
|
|
27
|
+
case "-h":
|
|
28
|
+
process.stdout.write(USAGE_HINT);
|
|
29
|
+
break;
|
|
30
|
+
case "init":
|
|
31
|
+
await initializeWorkspace(process.cwd());
|
|
32
|
+
process.stdout.write('Initialized Diana workspace.\nAdd sources to diana.config.json or .diana/local.config.json, then run "diana index".\n');
|
|
33
|
+
break;
|
|
34
|
+
case "index":
|
|
35
|
+
await indexWorkspace(process.cwd());
|
|
36
|
+
process.stdout.write("Index updated.\n");
|
|
37
|
+
break;
|
|
38
|
+
case "search": {
|
|
39
|
+
const results = await searchWorkspace(process.cwd(), arguments_.join(" "));
|
|
40
|
+
process.stdout.write(`${JSON.stringify(results)}\n`);
|
|
41
|
+
break;
|
|
42
|
+
}
|
|
43
|
+
case "read": {
|
|
44
|
+
const { path, section } = parseReadArguments(arguments_);
|
|
45
|
+
const result = await readWorkspace(process.cwd(), path, section);
|
|
46
|
+
process.stdout.write(`${JSON.stringify(result)}\n`);
|
|
47
|
+
break;
|
|
48
|
+
}
|
|
49
|
+
default:
|
|
50
|
+
throw new Error(`Unknown command: ${command}\nRun "diana --help" for usage.`);
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
catch (error) {
|
|
54
|
+
const message = error instanceof Error ? error.message.trim() : "Unexpected error.";
|
|
55
|
+
process.stderr.write(`Error: ${message}\n`);
|
|
56
|
+
process.exitCode = 1;
|
|
57
|
+
}
|
|
58
|
+
function parseReadArguments(arguments_) {
|
|
59
|
+
const [path, ...options] = arguments_;
|
|
60
|
+
if (path === undefined) {
|
|
61
|
+
throw new Error("Read path must not be empty.");
|
|
62
|
+
}
|
|
63
|
+
if (options.length === 0) {
|
|
64
|
+
return { path };
|
|
65
|
+
}
|
|
66
|
+
const section = options[1];
|
|
67
|
+
if (options.length === 2 &&
|
|
68
|
+
options[0] === "--section" &&
|
|
69
|
+
section !== undefined &&
|
|
70
|
+
section !== "") {
|
|
71
|
+
return { path, section };
|
|
72
|
+
}
|
|
73
|
+
throw new Error("Usage: diana read <path> [--section <anchor>].");
|
|
74
|
+
}
|
|
75
|
+
//# sourceMappingURL=cli.js.map
|
package/dist/cli.js.map
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"cli.js","sourceRoot":"","sources":["../src/cli.ts"],"names":[],"mappings":";AAEA,OAAO,EAAE,mBAAmB,EAAE,MAAM,oBAAoB,CAAC;AACzD,OAAO,EAAE,cAAc,EAAE,MAAM,qBAAqB,CAAC;AACrD,OAAO,EAAE,aAAa,EAAE,MAAM,oBAAoB,CAAC;AACnD,OAAO,EAAE,eAAe,EAAE,MAAM,sBAAsB,CAAC;AAEvD,MAAM,CAAC,OAAO,EAAE,GAAG,UAAU,CAAC,GAAG,OAAO,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC;AAEvD,MAAM,UAAU,GAAG;;;;;;;;CAQlB,CAAC;AAEF,IAAI,CAAC;IACH,IACE,CAAC,OAAO,KAAK,MAAM;QACjB,OAAO,KAAK,OAAO;QACnB,OAAO,KAAK,QAAQ;QACpB,OAAO,KAAK,IAAI,CAAC;QACnB,UAAU,CAAC,MAAM,GAAG,CAAC,EACrB,CAAC;QACD,MAAM,IAAI,KAAK,CAAC,gBAAgB,OAAO,GAAG,CAAC,CAAC;IAC9C,CAAC;IAED,QAAQ,OAAO,EAAE,CAAC;QAChB,KAAK,SAAS,CAAC;QACf,KAAK,QAAQ,CAAC;QACd,KAAK,IAAI;YACP,OAAO,CAAC,MAAM,CAAC,KAAK,CAAC,UAAU,CAAC,CAAC;YACjC,MAAM;QACR,KAAK,MAAM;YACT,MAAM,mBAAmB,CAAC,OAAO,CAAC,GAAG,EAAE,CAAC,CAAC;YACzC,OAAO,CAAC,MAAM,CAAC,KAAK,CAClB,uHAAuH,CACxH,CAAC;YACF,MAAM;QACR,KAAK,OAAO;YACV,MAAM,cAAc,CAAC,OAAO,CAAC,GAAG,EAAE,CAAC,CAAC;YACpC,OAAO,CAAC,MAAM,CAAC,KAAK,CAAC,kBAAkB,CAAC,CAAC;YACzC,MAAM;QACR,KAAK,QAAQ,EAAE,CAAC;YACd,MAAM,OAAO,GAAG,MAAM,eAAe,CACnC,OAAO,CAAC,GAAG,EAAE,EACb,UAAU,CAAC,IAAI,CAAC,GAAG,CAAC,CACrB,CAAC;YACF,OAAO,CAAC,MAAM,CAAC,KAAK,CAAC,GAAG,IAAI,CAAC,SAAS,CAAC,OAAO,CAAC,IAAI,CAAC,CAAC;YACrD,MAAM;QACR,CAAC;QACD,KAAK,MAAM,EAAE,CAAC;YACZ,MAAM,EAAE,IAAI,EAAE,OAAO,EAAE,GAAG,kBAAkB,CAAC,UAAU,CAAC,CAAC;YACzD,MAAM,MAAM,GAAG,MAAM,aAAa,CAAC,OAAO,CAAC,GAAG,EAAE,EAAE,IAAI,EAAE,OAAO,CAAC,CAAC;YACjE,OAAO,CAAC,MAAM,CAAC,KAAK,CAAC,GAAG,IAAI,CAAC,SAAS,CAAC,MAAM,CAAC,IAAI,CAAC,CAAC;YACpD,MAAM;QACR,CAAC;QACD;YACE,MAAM,IAAI,KAAK,CACb,oBAAoB,OAAO,iCAAiC,CAC7D,CAAC;IACN,CAAC;AACH,CAAC;AAAC,OAAO,KAAc,EAAE,CAAC;IACxB,MAAM,OAAO,GACX,KAAK,YAAY,KAAK,CAAC,CAAC,CAAC,KAAK,CAAC,OAAO,CAAC,IAAI,EAAE,CAAC,CAAC,CAAC,mBAAmB,CAAC;IACtE,OAAO,CAAC,MAAM,CAAC,KAAK,CAAC,UAAU,OAAO,IAAI,CAAC,CAAC;IAC5C,OAAO,CAAC,QAAQ,GAAG,CAAC,CAAC;AACvB,CAAC;AAED,SAAS,kBAAkB,CAAC,UAAoB;IAI9C,MAAM,CAAC,IAAI,EAAE,GAAG,OAAO,CAAC,GAAG,UAAU,CAAC;IAEtC,IAAI,IAAI,KAAK,SAAS,EAAE,CAAC;QACvB,MAAM,IAAI,KAAK,CAAC,8BAA8B,CAAC,CAAC;IAClD,CAAC;IAED,IAAI,OAAO,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;QACzB,OAAO,EAAE,IAAI,EAAE,CAAC;IAClB,CAAC;IAED,MAAM,OAAO,GAAG,OAAO,CAAC,CAAC,CAAC,CAAC;IAE3B,IACE,OAAO,CAAC,MAAM,KAAK,CAAC;QACpB,OAAO,CAAC,CAAC,CAAC,KAAK,WAAW;QAC1B,OAAO,KAAK,SAAS;QACrB,OAAO,KAAK,EAAE,EACd,CAAC;QACD,OAAO,EAAE,IAAI,EAAE,OAAO,EAAE,CAAC;IAC3B,CAAC;IAED,MAAM,IAAI,KAAK,CAAC,gDAAgD,CAAC,CAAC;AACpE,CAAC"}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import { join } from "node:path";
|
|
2
|
+
import { updateIndex } from "../indexer.js";
|
|
3
|
+
import { resolveSources } from "../sources.js";
|
|
4
|
+
import { WORKSPACE_CONSTANTS, loadWorkspace } from "../workspace.js";
|
|
5
|
+
const { databaseFileName, internalDirName } = WORKSPACE_CONSTANTS;
|
|
6
|
+
export const COMMAND_INDEX_ERRORS = {
|
|
7
|
+
noSourcesConfiguredError: "No sources configured. Add a source to diana.config.json or .diana/local.config.json.",
|
|
8
|
+
};
|
|
9
|
+
const { noSourcesConfiguredError } = COMMAND_INDEX_ERRORS;
|
|
10
|
+
export async function indexWorkspace(workspaceRoot) {
|
|
11
|
+
const workspace = await loadWorkspace(workspaceRoot);
|
|
12
|
+
if (workspace.sources.length === 0) {
|
|
13
|
+
throw new Error(noSourcesConfiguredError);
|
|
14
|
+
}
|
|
15
|
+
const sources = await resolveSources(workspace);
|
|
16
|
+
const databasePath = join(workspace.root, internalDirName, databaseFileName);
|
|
17
|
+
await updateIndex(databasePath, sources);
|
|
18
|
+
}
|
|
19
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/commands/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,IAAI,EAAE,MAAM,WAAW,CAAC;AAEjC,OAAO,EAAE,WAAW,EAAE,MAAM,eAAe,CAAC;AAC5C,OAAO,EAAE,cAAc,EAAE,MAAM,eAAe,CAAC;AAC/C,OAAO,EAAE,mBAAmB,EAAE,aAAa,EAAE,MAAM,iBAAiB,CAAC;AAErE,MAAM,EAAE,gBAAgB,EAAE,eAAe,EAAE,GAAG,mBAAmB,CAAC;AAElE,MAAM,CAAC,MAAM,oBAAoB,GAAG;IAClC,wBAAwB,EACtB,uFAAuF;CACjF,CAAC;AAEX,MAAM,EAAE,wBAAwB,EAAE,GAAG,oBAAoB,CAAC;AAE1D,MAAM,CAAC,KAAK,UAAU,cAAc,CAAC,aAAqB;IACxD,MAAM,SAAS,GAAG,MAAM,aAAa,CAAC,aAAa,CAAC,CAAC;IAErD,IAAI,SAAS,CAAC,OAAO,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;QACnC,MAAM,IAAI,KAAK,CAAC,wBAAwB,CAAC,CAAC;IAC5C,CAAC;IAED,MAAM,OAAO,GAAG,MAAM,cAAc,CAAC,SAAS,CAAC,CAAC;IAEhD,MAAM,YAAY,GAAG,IAAI,CAAC,SAAS,CAAC,IAAI,EAAE,eAAe,EAAE,gBAAgB,CAAC,CAAC;IAC7E,MAAM,WAAW,CAAC,YAAY,EAAE,OAAO,CAAC,CAAC;AAC3C,CAAC"}
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
import { join } from "node:path";
|
|
2
|
+
import { mkdir, writeFile } from "node:fs/promises";
|
|
3
|
+
import { WORKSPACE_CONSTANTS } from "../workspace.js";
|
|
4
|
+
import { configTemplate } from "../templates/config.template.js";
|
|
5
|
+
import { gitIgnoreTemplate } from "../templates/gitignore.template.js";
|
|
6
|
+
import { pathExists } from "../utils/pathExists.js";
|
|
7
|
+
const { configFileName, internalDirName, internalGitIgnoreName, localConfigFileName, } = WORKSPACE_CONSTANTS;
|
|
8
|
+
export const COMMAND_INIT_ERRORS = {
|
|
9
|
+
dianaWorkspaceExistsError: (workspaceRoot) => `\nDiana workspace already exists at ${workspaceRoot}.\nRemove ${configFileName} and ${internalDirName}/, then call <diana init> for re-initialization.\n`,
|
|
10
|
+
};
|
|
11
|
+
const { dianaWorkspaceExistsError } = COMMAND_INIT_ERRORS;
|
|
12
|
+
export async function initializeWorkspace(workspaceRoot) {
|
|
13
|
+
const pathToSharedConfig = join(workspaceRoot, configFileName);
|
|
14
|
+
const pathToInternalDir = join(workspaceRoot, internalDirName);
|
|
15
|
+
const pathToGitIgnore = join(pathToInternalDir, internalGitIgnoreName);
|
|
16
|
+
const pathToLocalConfig = join(pathToInternalDir, localConfigFileName);
|
|
17
|
+
const [sharedConfigExists, internalDirExists] = await Promise.all([
|
|
18
|
+
pathExists(pathToSharedConfig),
|
|
19
|
+
pathExists(pathToInternalDir),
|
|
20
|
+
]);
|
|
21
|
+
if (sharedConfigExists || internalDirExists) {
|
|
22
|
+
throw new Error(dianaWorkspaceExistsError(workspaceRoot));
|
|
23
|
+
}
|
|
24
|
+
const sharedConfig = `${JSON.stringify(configTemplate, null, 2)}\n`;
|
|
25
|
+
await writeFile(pathToSharedConfig, sharedConfig, {
|
|
26
|
+
flag: "wx",
|
|
27
|
+
});
|
|
28
|
+
await mkdir(pathToInternalDir);
|
|
29
|
+
await writeFile(pathToGitIgnore, gitIgnoreTemplate, {
|
|
30
|
+
flag: "wx",
|
|
31
|
+
});
|
|
32
|
+
await writeFile(pathToLocalConfig, sharedConfig, {
|
|
33
|
+
flag: "wx",
|
|
34
|
+
});
|
|
35
|
+
}
|
|
36
|
+
//# sourceMappingURL=init.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"init.js","sourceRoot":"","sources":["../../src/commands/init.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,IAAI,EAAE,MAAM,WAAW,CAAC;AACjC,OAAO,EAAE,KAAK,EAAE,SAAS,EAAE,MAAM,kBAAkB,CAAC;AAEpD,OAAO,EAAE,mBAAmB,EAAE,MAAM,iBAAiB,CAAC;AACtD,OAAO,EAAE,cAAc,EAAE,MAAM,iCAAiC,CAAC;AACjE,OAAO,EAAE,iBAAiB,EAAE,MAAM,oCAAoC,CAAC;AACvE,OAAO,EAAE,UAAU,EAAE,MAAM,wBAAwB,CAAC;AAEpD,MAAM,EACJ,cAAc,EACd,eAAe,EACf,qBAAqB,EACrB,mBAAmB,GACpB,GAAG,mBAAmB,CAAC;AAExB,MAAM,CAAC,MAAM,mBAAmB,GAAG;IACjC,yBAAyB,EAAE,CAAC,aAAqB,EAAE,EAAE,CACnD,uCAAuC,aAAa,aAAa,cAAc,QAAQ,eAAe,oDAAoD;CACpJ,CAAC;AAEX,MAAM,EAAE,yBAAyB,EAAE,GAAG,mBAAmB,CAAC;AAE1D,MAAM,CAAC,KAAK,UAAU,mBAAmB,CACvC,aAAqB;IAErB,MAAM,kBAAkB,GAAG,IAAI,CAAC,aAAa,EAAE,cAAc,CAAC,CAAC;IAC/D,MAAM,iBAAiB,GAAG,IAAI,CAAC,aAAa,EAAE,eAAe,CAAC,CAAC;IAC/D,MAAM,eAAe,GAAG,IAAI,CAAC,iBAAiB,EAAE,qBAAqB,CAAC,CAAC;IACvE,MAAM,iBAAiB,GAAG,IAAI,CAAC,iBAAiB,EAAE,mBAAmB,CAAC,CAAC;IAEvE,MAAM,CAAC,kBAAkB,EAAE,iBAAiB,CAAC,GAAG,MAAM,OAAO,CAAC,GAAG,CAAC;QAChE,UAAU,CAAC,kBAAkB,CAAC;QAC9B,UAAU,CAAC,iBAAiB,CAAC;KAC9B,CAAC,CAAC;IAEH,IAAI,kBAAkB,IAAI,iBAAiB,EAAE,CAAC;QAC5C,MAAM,IAAI,KAAK,CAAC,yBAAyB,CAAC,aAAa,CAAC,CAAC,CAAC;IAC5D,CAAC;IAED,MAAM,YAAY,GAAG,GAAG,IAAI,CAAC,SAAS,CAAC,cAAc,EAAE,IAAI,EAAE,CAAC,CAAC,IAAI,CAAC;IAEpE,MAAM,SAAS,CAAC,kBAAkB,EAAE,YAAY,EAAE;QAChD,IAAI,EAAE,IAAI;KACX,CAAC,CAAC;IACH,MAAM,KAAK,CAAC,iBAAiB,CAAC,CAAC;IAC/B,MAAM,SAAS,CAAC,eAAe,EAAE,iBAAiB,EAAE;QAClD,IAAI,EAAE,IAAI;KACX,CAAC,CAAC;IACH,MAAM,SAAS,CAAC,iBAAiB,EAAE,YAAY,EAAE;QAC/C,IAAI,EAAE,IAAI;KACX,CAAC,CAAC;AACL,CAAC"}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
export declare const COMMAND_READ_ERRORS: {
|
|
2
|
+
readonly emptyPathError: "Read path must not be empty.";
|
|
3
|
+
readonly fileTooLargeError: (filePath: string) => string;
|
|
4
|
+
readonly invalidPathError: (filePath: string) => string;
|
|
5
|
+
readonly sectionNotFoundError: (anchor: string) => string;
|
|
6
|
+
};
|
|
7
|
+
export type ReadResult = {
|
|
8
|
+
content: string;
|
|
9
|
+
path: string;
|
|
10
|
+
sourceRoot: string;
|
|
11
|
+
truncated: boolean;
|
|
12
|
+
};
|
|
13
|
+
export declare function readWorkspace(workspaceRoot: string, requestedPath: string, section?: string): Promise<ReadResult>;
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
import { readFile, realpath, stat } from "node:fs/promises";
|
|
2
|
+
import { isAbsolute, posix, relative, resolve, sep } from "node:path";
|
|
3
|
+
import { extractMarkdownSection } from "../indexer.js";
|
|
4
|
+
import { resolveSources } from "../sources.js";
|
|
5
|
+
import { loadWorkspace } from "../workspace.js";
|
|
6
|
+
const MAX_FILE_BYTES = 1024 * 1024;
|
|
7
|
+
const MAX_OUTPUT_CHARACTERS = 64 * 1024;
|
|
8
|
+
const PROTECTED_DIRECTORY_NAMES = new Set([".diana", ".git"]);
|
|
9
|
+
export const COMMAND_READ_ERRORS = {
|
|
10
|
+
emptyPathError: "Read path must not be empty.",
|
|
11
|
+
fileTooLargeError: (filePath) => `Source file exceeds the ${MAX_FILE_BYTES}-byte read limit: ${filePath}.`,
|
|
12
|
+
invalidPathError: (filePath) => `Path is not a readable Markdown file in a configured source: ${filePath}.`,
|
|
13
|
+
sectionNotFoundError: (anchor) => `Section not found: ${anchor}.`,
|
|
14
|
+
};
|
|
15
|
+
const { emptyPathError, fileTooLargeError, invalidPathError, sectionNotFoundError, } = COMMAND_READ_ERRORS;
|
|
16
|
+
export async function readWorkspace(workspaceRoot, requestedPath, section) {
|
|
17
|
+
if (requestedPath.trim() === "") {
|
|
18
|
+
throw new Error(emptyPathError);
|
|
19
|
+
}
|
|
20
|
+
const workspace = await loadWorkspace(workspaceRoot);
|
|
21
|
+
const configuredPath = resolve(workspace.root, requestedPath);
|
|
22
|
+
const filePath = await realpath(configuredPath);
|
|
23
|
+
const source = findContainingSource(filePath, await resolveSources(workspace));
|
|
24
|
+
if (source === undefined || !isReadableSourceFile(filePath, source)) {
|
|
25
|
+
throw new Error(invalidPathError(configuredPath));
|
|
26
|
+
}
|
|
27
|
+
const fileStats = await stat(filePath);
|
|
28
|
+
if (!fileStats.isFile()) {
|
|
29
|
+
throw new Error(invalidPathError(configuredPath));
|
|
30
|
+
}
|
|
31
|
+
if (fileStats.size > MAX_FILE_BYTES) {
|
|
32
|
+
throw new Error(fileTooLargeError(filePath));
|
|
33
|
+
}
|
|
34
|
+
const markdown = await readFile(filePath, "utf8");
|
|
35
|
+
const content = section === undefined ? markdown : readSection(markdown, section);
|
|
36
|
+
return {
|
|
37
|
+
content: content.slice(0, MAX_OUTPUT_CHARACTERS),
|
|
38
|
+
path: toDocumentPath(source.sourceRoot, filePath),
|
|
39
|
+
sourceRoot: source.sourceRoot,
|
|
40
|
+
truncated: content.length > MAX_OUTPUT_CHARACTERS,
|
|
41
|
+
};
|
|
42
|
+
}
|
|
43
|
+
function readSection(markdown, section) {
|
|
44
|
+
const content = extractMarkdownSection(markdown, section);
|
|
45
|
+
if (content === undefined) {
|
|
46
|
+
throw new Error(sectionNotFoundError(section));
|
|
47
|
+
}
|
|
48
|
+
return content;
|
|
49
|
+
}
|
|
50
|
+
function findContainingSource(filePath, sources) {
|
|
51
|
+
return sources.find((source) => isPathInside(source.sourceRoot, filePath));
|
|
52
|
+
}
|
|
53
|
+
function isReadableSourceFile(filePath, source) {
|
|
54
|
+
const documentPath = toDocumentPath(source.sourceRoot, filePath);
|
|
55
|
+
const pathSegments = documentPath.split("/");
|
|
56
|
+
return (filePath.endsWith(".md") &&
|
|
57
|
+
!pathSegments.some(isProtectedOrHidden) &&
|
|
58
|
+
source.include.some((pattern) => posix.matchesGlob(documentPath, pattern)) &&
|
|
59
|
+
!source.exclude.some((pattern) => posix.matchesGlob(documentPath, pattern)));
|
|
60
|
+
}
|
|
61
|
+
function isProtectedOrHidden(pathSegment) {
|
|
62
|
+
return (pathSegment.startsWith(".") || PROTECTED_DIRECTORY_NAMES.has(pathSegment));
|
|
63
|
+
}
|
|
64
|
+
function isPathInside(parentPath, childPath) {
|
|
65
|
+
const relativePath = relative(parentPath, childPath);
|
|
66
|
+
return (relativePath === "" ||
|
|
67
|
+
(relativePath !== ".." &&
|
|
68
|
+
!relativePath.startsWith(`..${sep}`) &&
|
|
69
|
+
!isAbsolute(relativePath)));
|
|
70
|
+
}
|
|
71
|
+
function toDocumentPath(sourceRoot, filePath) {
|
|
72
|
+
return relative(sourceRoot, filePath).split(sep).join("/");
|
|
73
|
+
}
|
|
74
|
+
//# sourceMappingURL=read.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"read.js","sourceRoot":"","sources":["../../src/commands/read.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
export declare const COMMAND_SEARCH_ERRORS: {
|
|
2
|
+
readonly emptyQueryError: "Search query must not be empty.";
|
|
3
|
+
readonly indexNotFoundError: "No index found. Run diana index first.";
|
|
4
|
+
};
|
|
5
|
+
export type SearchResult = {
|
|
6
|
+
anchor: string;
|
|
7
|
+
excerpt: string;
|
|
8
|
+
heading: string;
|
|
9
|
+
path: string;
|
|
10
|
+
sourceRoot: string;
|
|
11
|
+
title: string;
|
|
12
|
+
};
|
|
13
|
+
export declare function searchWorkspace(workspaceRoot: string, query: string): Promise<SearchResult[]>;
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
import { join } from "node:path";
|
|
2
|
+
import { DatabaseSync } from "node:sqlite";
|
|
3
|
+
import { updateIndex } from "../indexer.js";
|
|
4
|
+
import { resolveSources } from "../sources.js";
|
|
5
|
+
import { WORKSPACE_CONSTANTS, loadWorkspace } from "../workspace.js";
|
|
6
|
+
import { pathExists } from "../utils/pathExists.js";
|
|
7
|
+
const { databaseFileName, internalDirName } = WORKSPACE_CONSTANTS;
|
|
8
|
+
export const COMMAND_SEARCH_ERRORS = {
|
|
9
|
+
emptyQueryError: "Search query must not be empty.",
|
|
10
|
+
indexNotFoundError: "No index found. Run diana index first.",
|
|
11
|
+
};
|
|
12
|
+
const { emptyQueryError, indexNotFoundError } = COMMAND_SEARCH_ERRORS;
|
|
13
|
+
export async function searchWorkspace(workspaceRoot, query) {
|
|
14
|
+
if (query.trim() === "") {
|
|
15
|
+
throw new Error(emptyQueryError);
|
|
16
|
+
}
|
|
17
|
+
const workspace = await loadWorkspace(workspaceRoot);
|
|
18
|
+
const databasePath = join(workspace.root, internalDirName, databaseFileName);
|
|
19
|
+
if (!(await pathExists(databasePath))) {
|
|
20
|
+
throw new Error(indexNotFoundError);
|
|
21
|
+
}
|
|
22
|
+
await updateIndex(databasePath, await resolveSources(workspace), "auto");
|
|
23
|
+
const database = new DatabaseSync(databasePath, { readOnly: true });
|
|
24
|
+
try {
|
|
25
|
+
return database
|
|
26
|
+
.prepare(`
|
|
27
|
+
SELECT
|
|
28
|
+
source_root AS sourceRoot,
|
|
29
|
+
path,
|
|
30
|
+
title,
|
|
31
|
+
heading,
|
|
32
|
+
anchor,
|
|
33
|
+
snippet(sections, 5, '', '', '…', 24) AS excerpt
|
|
34
|
+
FROM sections
|
|
35
|
+
WHERE sections MATCH ?
|
|
36
|
+
ORDER BY bm25(sections)
|
|
37
|
+
LIMIT 10
|
|
38
|
+
`)
|
|
39
|
+
.all(toFtsQuery(query));
|
|
40
|
+
}
|
|
41
|
+
finally {
|
|
42
|
+
database.close();
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
function toFtsQuery(query) {
|
|
46
|
+
return query
|
|
47
|
+
.trim()
|
|
48
|
+
.split(/\s+/)
|
|
49
|
+
.map((term) => `"${term.replaceAll('"', '""')}"`)
|
|
50
|
+
.join(" AND ");
|
|
51
|
+
}
|
|
52
|
+
//# sourceMappingURL=search.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"search.js","sourceRoot":"","sources":["../../src/commands/search.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,IAAI,EAAE,MAAM,WAAW,CAAC;AACjC,OAAO,EAAE,YAAY,EAAE,MAAM,aAAa,CAAC;AAE3C,OAAO,EAAE,WAAW,EAAE,MAAM,eAAe,CAAC;AAC5C,OAAO,EAAE,cAAc,EAAE,MAAM,eAAe,CAAC;AAC/C,OAAO,EAAE,mBAAmB,EAAE,aAAa,EAAE,MAAM,iBAAiB,CAAC;AACrE,OAAO,EAAE,UAAU,EAAE,MAAM,wBAAwB,CAAC;AAEpD,MAAM,EAAE,gBAAgB,EAAE,eAAe,EAAE,GAAG,mBAAmB,CAAC;AAElE,MAAM,CAAC,MAAM,qBAAqB,GAAG;IACnC,eAAe,EAAE,iCAAiC;IAClD,kBAAkB,EAAE,wCAAwC;CACpD,CAAC;AAEX,MAAM,EAAE,eAAe,EAAE,kBAAkB,EAAE,GAAG,qBAAqB,CAAC;AAWtE,MAAM,CAAC,KAAK,UAAU,eAAe,CACnC,aAAqB,EACrB,KAAa;IAEb,IAAI,KAAK,CAAC,IAAI,EAAE,KAAK,EAAE,EAAE,CAAC;QACxB,MAAM,IAAI,KAAK,CAAC,eAAe,CAAC,CAAC;IACnC,CAAC;IAED,MAAM,SAAS,GAAG,MAAM,aAAa,CAAC,aAAa,CAAC,CAAC;IACrD,MAAM,YAAY,GAAG,IAAI,CAAC,SAAS,CAAC,IAAI,EAAE,eAAe,EAAE,gBAAgB,CAAC,CAAC;IAE7E,IAAI,CAAC,CAAC,MAAM,UAAU,CAAC,YAAY,CAAC,CAAC,EAAE,CAAC;QACtC,MAAM,IAAI,KAAK,CAAC,kBAAkB,CAAC,CAAC;IACtC,CAAC;IAED,MAAM,WAAW,CAAC,YAAY,EAAE,MAAM,cAAc,CAAC,SAAS,CAAC,EAAE,MAAM,CAAC,CAAC;IAEzE,MAAM,QAAQ,GAAG,IAAI,YAAY,CAAC,YAAY,EAAE,EAAE,QAAQ,EAAE,IAAI,EAAE,CAAC,CAAC;IAEpE,IAAI,CAAC;QACH,OAAO,QAAQ;aACZ,OAAO,CACN;;;;;;;;;;;;OAYD,CACA;aACA,GAAG,CAAC,UAAU,CAAC,KAAK,CAAC,CAAmB,CAAC;IAC9C,CAAC;YAAS,CAAC;QACT,QAAQ,CAAC,KAAK,EAAE,CAAC;IACnB,CAAC;AACH,CAAC;AAED,SAAS,UAAU,CAAC,KAAa;IAC/B,OAAO,KAAK;SACT,IAAI,EAAE;SACN,KAAK,CAAC,KAAK,CAAC;SACZ,GAAG,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,IAAI,IAAI,CAAC,UAAU,CAAC,GAAG,EAAE,IAAI,CAAC,GAAG,CAAC;SAChD,IAAI,CAAC,OAAO,CAAC,CAAC;AACnB,CAAC"}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import type { ResolvedSource } from "./types/source.types.js";
|
|
2
|
+
export declare const INDEX_ERRORS: {
|
|
3
|
+
readonly invalidTextError: (filePath: string) => string;
|
|
4
|
+
readonly fileTooLargeError: (filePath: string) => string;
|
|
5
|
+
readonly schemaRebuildError: "Index schema changed. Run diana index to rebuild it.";
|
|
6
|
+
};
|
|
7
|
+
export type IndexedSection = {
|
|
8
|
+
anchor: string;
|
|
9
|
+
body: string;
|
|
10
|
+
heading: string;
|
|
11
|
+
title: string;
|
|
12
|
+
};
|
|
13
|
+
export declare function updateIndex(databasePath: string, sources: ResolvedSource[], mode?: "all" | "auto"): Promise<void>;
|
|
14
|
+
export declare function findMarkdownFiles(source: ResolvedSource): Promise<string[]>;
|
|
15
|
+
export declare function extractMarkdownSection(markdown: string, requestedAnchor: string): string | undefined;
|
|
16
|
+
export declare function splitMarkdownIntoSections(markdown: string, filePath: string): IndexedSection[];
|