@wwjd/pi-graphify 0.1.0 → 0.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +27 -0
- package/README.md +54 -61
- package/docs/TESTING.md +149 -0
- package/extensions/index.ts +54 -20
- package/package.json +4 -3
- package/src/backends/cli.ts +201 -0
- package/src/backends/mock.ts +47 -0
- package/src/backends/types.ts +158 -0
- package/src/coordinator.ts +192 -0
- package/src/errors.ts +67 -0
- package/src/extensions/extension-load.test.ts +15 -0
- package/src/graphify.ts +10 -59
- package/src/tools/affected.ts +37 -0
- package/src/tools/build.ts +43 -0
- package/src/tools/explain.ts +38 -0
- package/src/tools/format.ts +25 -0
- package/src/tools/index.ts +85 -7
- package/src/tools/path.ts +43 -0
- package/src/tools/query.ts +37 -0
- package/src/tools/status.ts +38 -10
- package/src/tools/tools.test.ts +282 -0
- package/src/tools/types.ts +31 -0
- package/src/tools/version.ts +35 -0
- package/src/version.ts +172 -0
package/CHANGELOG.md
CHANGED
|
@@ -5,6 +5,33 @@ All notable changes to this project will be documented in this file.
|
|
|
5
5
|
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
|
|
6
6
|
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
|
7
7
|
|
|
8
|
+
## [0.3.0](https://github.com/WianVDM/pi-graphify/compare/pi-graphify-v0.2.0...pi-graphify-v0.3.0) (2026-07-12)
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
### Features
|
|
12
|
+
|
|
13
|
+
* **tools:** add build, query, path, explain, affected, and version tools ([5b8cb07](https://github.com/WianVDM/pi-graphify/commit/5b8cb0730f7c766dd00f40149e7abb6b1fce64fa))
|
|
14
|
+
* **tools:** add shared tool types and formatting helpers ([0efd421](https://github.com/WianVDM/pi-graphify/commit/0efd4214e6b4397c126abecb864ad947be896fa6))
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
### Bug Fixes
|
|
18
|
+
|
|
19
|
+
* replace /graphify-build references with graphify CLI command ([63f4fb2](https://github.com/WianVDM/pi-graphify/commit/63f4fb25889719448f13c4dffb485164f7774c55))
|
|
20
|
+
|
|
21
|
+
## [0.2.0](https://github.com/WianVDM/pi-graphify/compare/pi-graphify-v0.1.0...pi-graphify-v0.2.0) (2026-07-12)
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
### Features
|
|
25
|
+
|
|
26
|
+
* add graphify_status tool and /graphify-status command ([99f809c](https://github.com/WianVDM/pi-graphify/commit/99f809caace328e5af97a5dcdac50af025437776))
|
|
27
|
+
* implement phase 2 backend abstraction ([40afa65](https://github.com/WianVDM/pi-graphify/commit/40afa65bbadf9bcb72676a613d94f6bdb677cc5d))
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
### Bug Fixes
|
|
31
|
+
|
|
32
|
+
* correct status tool description and missing-Graphify warning ([c4225c1](https://github.com/WianVDM/pi-graphify/commit/c4225c1591124a65ca0df33565168970a6720a9d))
|
|
33
|
+
* normalize package author email casing ([fcfa352](https://github.com/WianVDM/pi-graphify/commit/fcfa3520463ed860be1264daa6443c608a197444))
|
|
34
|
+
|
|
8
35
|
## [Unreleased]
|
|
9
36
|
|
|
10
37
|
## 0.1.0 (2026-07-11)
|
package/README.md
CHANGED
|
@@ -1,93 +1,86 @@
|
|
|
1
1
|
# pi-graphify
|
|
2
2
|
|
|
3
|
-
A
|
|
3
|
+
A [Pi](https://pi.dev) extension that brings [Graphify](https://github.com/Graphify-Labs/graphify) knowledge graph capabilities into your agent sessions.
|
|
4
4
|
|
|
5
|
-
|
|
5
|
+
## Install
|
|
6
6
|
|
|
7
|
-
|
|
7
|
+
```bash
|
|
8
|
+
pi install @wwjd/pi-graphify
|
|
9
|
+
```
|
|
8
10
|
|
|
9
|
-
|
|
11
|
+
Or run locally during development:
|
|
10
12
|
|
|
11
13
|
```bash
|
|
12
|
-
# Install from a local path (during development)
|
|
13
14
|
pi install .
|
|
14
|
-
|
|
15
|
-
# Or run ephemerally without installing
|
|
16
|
-
pi -e .
|
|
17
|
-
|
|
18
|
-
# Type check
|
|
19
|
-
npm run typecheck
|
|
20
|
-
|
|
21
|
-
# Lint
|
|
22
|
-
npm run lint
|
|
23
15
|
```
|
|
24
16
|
|
|
25
|
-
## What
|
|
17
|
+
## What it does
|
|
26
18
|
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
│ └── index.ts # Extension entry point (tools, commands, events)
|
|
31
|
-
├── src/ # Core implementation
|
|
32
|
-
├── package.json # Pi manifest + npm config
|
|
33
|
-
├── tsconfig.json # TypeScript config (type checking only)
|
|
34
|
-
├── biome.json # Linter/formatter config
|
|
35
|
-
├── .gitignore
|
|
36
|
-
├── LICENSE
|
|
37
|
-
├── CHANGELOG.md
|
|
38
|
-
├── RELEASE.md # Versioning and release standards
|
|
39
|
-
├── AGENTS.md # Agent context / development conventions
|
|
40
|
-
└── README.md
|
|
41
|
-
```
|
|
19
|
+
`pi-graphify` detects the Graphify CLI and a project's `graphify-out/graph.json` graph, then exposes Graphify operations as Pi tools and slash commands. It routes requests through a backend abstraction so the agent can use Graphify without worrying about whether it's talking to the CLI or (in the future) an MCP server.
|
|
20
|
+
|
|
21
|
+
When a graph is present, the extension also injects a lightweight hint into the system prompt so the agent knows it can ask structural codebase questions via the graph.
|
|
42
22
|
|
|
43
|
-
##
|
|
23
|
+
## Requirements
|
|
44
24
|
|
|
45
|
-
|
|
25
|
+
- [Graphify](https://github.com/Graphify-Labs/graphify) must be installed and on your `PATH`.
|
|
26
|
+
- The project must have a generated graph at `graphify-out/graph.json`.
|
|
46
27
|
|
|
47
|
-
|
|
28
|
+
## Current commands
|
|
48
29
|
|
|
49
|
-
|
|
30
|
+
| Command | Description |
|
|
31
|
+
|---|---|
|
|
32
|
+
| `/graphify-status` | Check whether a graph exists and whether the Graphify CLI is compatible. |
|
|
50
33
|
|
|
51
|
-
|
|
34
|
+
## Current tools
|
|
52
35
|
|
|
53
|
-
|
|
36
|
+
| Tool | Description |
|
|
37
|
+
|---|---|
|
|
38
|
+
| `graphify_status` | Check graph presence, path, and Graphify CLI compatibility. |
|
|
39
|
+
| `graphify_build` | Build or incrementally update the Graphify knowledge graph. |
|
|
40
|
+
| `graphify_query` | Ask a natural-language question against the graph. |
|
|
41
|
+
| `graphify_path` | Find the shortest path between two graph nodes. |
|
|
42
|
+
| `graphify_explain` | Explain a node and its connections in the graph. |
|
|
43
|
+
| `graphify_affected` | Show the blast radius of changes to one or more files. |
|
|
44
|
+
| `graphify_version` | Report the installed Graphify version and compatibility status. |
|
|
54
45
|
|
|
55
|
-
|
|
46
|
+
## What's coming
|
|
56
47
|
|
|
57
|
-
|
|
58
|
-
- npm 10+
|
|
59
|
-
- pi CLI
|
|
48
|
+
Slash commands for the tools above are planned in the next phase:
|
|
60
49
|
|
|
61
|
-
|
|
50
|
+
- `/graphify-build`
|
|
51
|
+
- `/graphify-query`
|
|
52
|
+
- `/graphify-path`
|
|
53
|
+
- `/graphify-explain`
|
|
54
|
+
- `/graphify-affected`
|
|
55
|
+
- `/graphify-version`
|
|
62
56
|
|
|
63
|
-
|
|
64
|
-
|---------|-------------|
|
|
65
|
-
| `npm run typecheck` | TypeScript type checking (`tsc --noEmit`) |
|
|
66
|
-
| `npm run lint` | Check lint + formatting (`biome check`) |
|
|
67
|
-
| `npm run lint:fix` | Auto-fix lint + formatting issues |
|
|
68
|
-
| `npm run format` | Format code only |
|
|
57
|
+
Later phases will add background file watching, MCP backend support, ecosystem integrations, and release readiness work. See [plan/PLAN.md](plan/PLAN.md) for the full roadmap.
|
|
69
58
|
|
|
70
|
-
|
|
59
|
+
## How it works
|
|
71
60
|
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
61
|
+
1. On session start, the extension detects the installed Graphify version and looks for `graphify-out/graph.json`.
|
|
62
|
+
2. If a graph is found, a hint is added to the agent context.
|
|
63
|
+
3. Tools and commands route through a coordinator that selects the best available backend.
|
|
75
64
|
|
|
76
|
-
|
|
77
|
-
pi -ne -e . --no-session -p "List the available tools."
|
|
78
|
-
```
|
|
65
|
+
## Status
|
|
79
66
|
|
|
80
|
-
|
|
67
|
+
This extension is in early development. The public API, tools, and commands will evolve as the extension matures toward a stable 1.0 release.
|
|
81
68
|
|
|
82
|
-
|
|
69
|
+
## Documentation
|
|
83
70
|
|
|
84
|
-
|
|
71
|
+
- [Design](docs/DESIGN.md)
|
|
72
|
+
- [Architecture](docs/ARCHITECTURE.md)
|
|
73
|
+
- [Ecosystem](docs/ECOSYSTEM.md)
|
|
74
|
+
- [Versioning](docs/VERSIONING.md)
|
|
75
|
+
- [Release standards](RELEASE.md)
|
|
76
|
+
- [Agent context](AGENTS.md)
|
|
85
77
|
|
|
86
|
-
|
|
78
|
+
## Development
|
|
87
79
|
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
80
|
+
```bash
|
|
81
|
+
npm run typecheck
|
|
82
|
+
npm run lint
|
|
83
|
+
```
|
|
91
84
|
|
|
92
85
|
## License
|
|
93
86
|
|
package/docs/TESTING.md
ADDED
|
@@ -0,0 +1,149 @@
|
|
|
1
|
+
# Local Development and Testing Guide
|
|
2
|
+
|
|
3
|
+
This guide covers how to install and test the local `pi-graphify` extension, plus how to install and uninstall the Graphify CLI for end-to-end verification.
|
|
4
|
+
|
|
5
|
+
## Prerequisites
|
|
6
|
+
|
|
7
|
+
- [Pi](https://pi.dev) installed and on your PATH.
|
|
8
|
+
- Node.js 20+ and npm.
|
|
9
|
+
- Python 3.10+ (only needed when testing with the Graphify CLI).
|
|
10
|
+
- [uv](https://docs.astral.sh/uv/) or [pipx](https://pipx.pypa.io/) (recommended for Graphify).
|
|
11
|
+
|
|
12
|
+
## Install the local pi-graphify extension
|
|
13
|
+
|
|
14
|
+
From the project root directory, run:
|
|
15
|
+
|
|
16
|
+
```bash
|
|
17
|
+
pi install .
|
|
18
|
+
```
|
|
19
|
+
|
|
20
|
+
This installs the local package into Pi so the extension is loaded on the next Pi session.
|
|
21
|
+
|
|
22
|
+
To verify the install, start Pi in any project and check that `/graphify-status` autocompletes.
|
|
23
|
+
|
|
24
|
+
## Automated checks
|
|
25
|
+
|
|
26
|
+
Before running manual tests, run the static and automated checks:
|
|
27
|
+
|
|
28
|
+
```bash
|
|
29
|
+
npm run typecheck
|
|
30
|
+
npm run lint
|
|
31
|
+
npm test
|
|
32
|
+
npm audit --audit-level=moderate
|
|
33
|
+
```
|
|
34
|
+
|
|
35
|
+
All should pass.
|
|
36
|
+
|
|
37
|
+
## Install the Graphify CLI
|
|
38
|
+
|
|
39
|
+
The official PyPI package is **`graphifyy`** (double-y). The CLI command is still `graphify`.
|
|
40
|
+
|
|
41
|
+
### Recommended: uv
|
|
42
|
+
|
|
43
|
+
```bash
|
|
44
|
+
uv tool install graphifyy
|
|
45
|
+
graphify --version
|
|
46
|
+
```
|
|
47
|
+
|
|
48
|
+
### Alternative: pipx
|
|
49
|
+
|
|
50
|
+
```bash
|
|
51
|
+
pipx install graphifyy
|
|
52
|
+
graphify --version
|
|
53
|
+
```
|
|
54
|
+
|
|
55
|
+
### Alternative: pip
|
|
56
|
+
|
|
57
|
+
```bash
|
|
58
|
+
pip install graphifyy
|
|
59
|
+
graphify --version
|
|
60
|
+
```
|
|
61
|
+
|
|
62
|
+
After installing, `graphify` should be available on your PATH. If it is not, add the tool's bin directory to your PATH (for example, `~/.local/bin` for `uv tool` or `pipx`).
|
|
63
|
+
|
|
64
|
+
## Build a graph for testing
|
|
65
|
+
|
|
66
|
+
Phase 3 only adds the tools. The `/graphify-build` command is added in Phase 4, so for now build the graph directly with the Graphify CLI.
|
|
67
|
+
|
|
68
|
+
The recommended approach for testing `pi-graphify` is a code-only graph. It skips semantic extraction, so it does not require an API key or extra Python dependencies:
|
|
69
|
+
|
|
70
|
+
```bash
|
|
71
|
+
graphify . --code-only
|
|
72
|
+
```
|
|
73
|
+
|
|
74
|
+
This creates a `graphify-out/` directory with `graph.json`, `graph.html`, and `GRAPH_REPORT.md`. It has been verified to work with the default `uv tool install graphifyy` setup.
|
|
75
|
+
|
|
76
|
+
If you want semantic extraction (docs, images, etc.), you need the `kimi` backend dependencies. Install them with:
|
|
77
|
+
|
|
78
|
+
```bash
|
|
79
|
+
uv tool install "graphifyy[kimi]" --force
|
|
80
|
+
```
|
|
81
|
+
|
|
82
|
+
Then run the full build:
|
|
83
|
+
|
|
84
|
+
```bash
|
|
85
|
+
graphify .
|
|
86
|
+
```
|
|
87
|
+
|
|
88
|
+
Once Phase 4 is complete, `/graphify-build` will be available as an alternative.
|
|
89
|
+
|
|
90
|
+
## Verify the extension with Graphify installed
|
|
91
|
+
|
|
92
|
+
1. Start Pi in a project with a built graph.
|
|
93
|
+
2. You should see a notification: `Graphify graph ready: ...`.
|
|
94
|
+
3. The supported tools should be available to the LLM. You can verify by asking: "What graphify tools are available?" or by checking the system prompt tool list.
|
|
95
|
+
4. Test a tool by asking: "Use the graphify graph to explain how auth works in this codebase."
|
|
96
|
+
|
|
97
|
+
## Verify the extension without Graphify
|
|
98
|
+
|
|
99
|
+
1. Uninstall Graphify (see below).
|
|
100
|
+
2. Start Pi in a project.
|
|
101
|
+
3. You should see a warning: `Graphify CLI was not detected on PATH...`.
|
|
102
|
+
4. No `graphify_*` tools should be available to the LLM.
|
|
103
|
+
5. `/graphify-status` should still work and report that no graph was found.
|
|
104
|
+
|
|
105
|
+
## Uninstall the Graphify CLI
|
|
106
|
+
|
|
107
|
+
### uv
|
|
108
|
+
|
|
109
|
+
```bash
|
|
110
|
+
uv tool uninstall graphifyy
|
|
111
|
+
```
|
|
112
|
+
|
|
113
|
+
### pipx
|
|
114
|
+
|
|
115
|
+
```bash
|
|
116
|
+
pipx uninstall graphifyy
|
|
117
|
+
```
|
|
118
|
+
|
|
119
|
+
### pip
|
|
120
|
+
|
|
121
|
+
```bash
|
|
122
|
+
pip uninstall graphifyy
|
|
123
|
+
```
|
|
124
|
+
|
|
125
|
+
After uninstalling, run `graphify --version` to confirm it is no longer on PATH.
|
|
126
|
+
|
|
127
|
+
## Uninstall the local pi-graphify extension
|
|
128
|
+
|
|
129
|
+
```bash
|
|
130
|
+
pi uninstall npm:@wwjd/pi-graphify
|
|
131
|
+
```
|
|
132
|
+
|
|
133
|
+
Or remove the package manually from Pi's package directory if you installed it locally.
|
|
134
|
+
|
|
135
|
+
## Troubleshooting
|
|
136
|
+
|
|
137
|
+
| Problem | Solution |
|
|
138
|
+
|---|---|
|
|
139
|
+
| `graphify` command not found after install | Ensure the tool bin directory is on your PATH. For `uv tool`, try `uv tool update-shell`. |
|
|
140
|
+
| Pi does not load the extension | Run `pi --version` and confirm the package is listed in `pi packages`. Try `/reload`. |
|
|
141
|
+
| Extension shows old version after changes | Run `pi install .` again, then start a fresh Pi session. |
|
|
142
|
+
| `graphify_status` returns "No Graphify graph found" | Build the graph first with `graphify . --code-only`. |
|
|
143
|
+
| Semantic extraction fails with `openai` package error | Run `graphify . --code-only` for a code-only graph, or install the `kimi` backend with `uv tool install "graphifyy[kimi]" --force`. |
|
|
144
|
+
|
|
145
|
+
## References
|
|
146
|
+
|
|
147
|
+
- [Graphify repository](https://github.com/Graphify-Labs/graphify)
|
|
148
|
+
- [graphifyy on PyPI](https://pypi.org/project/graphifyy/)
|
|
149
|
+
- [Pi extensions documentation](https://pi.dev/docs/extensions)
|
package/extensions/index.ts
CHANGED
|
@@ -4,28 +4,70 @@
|
|
|
4
4
|
* Brings Graphify knowledge graph capabilities into Pi sessions:
|
|
5
5
|
* - Detects existing graphify-out/graph.json in the project
|
|
6
6
|
* - Registers LLM-callable tools (graphify_status, graphify_query, ...)
|
|
7
|
-
* - Registers slash commands (/graphify-
|
|
7
|
+
* - Registers slash commands (/graphify-status, ...)
|
|
8
8
|
* - Optionally watches source files for incremental rebuilds
|
|
9
9
|
*/
|
|
10
10
|
|
|
11
11
|
import type { ExtensionAPI } from "@earendil-works/pi-coding-agent";
|
|
12
|
-
import {
|
|
12
|
+
import { GraphifyCoordinator } from "../src/coordinator.js";
|
|
13
13
|
import { buildGraphifyHint, graphifyStatus } from "../src/graphify.js";
|
|
14
14
|
import { registerGraphifyTools } from "../src/tools/index.js";
|
|
15
15
|
|
|
16
16
|
export default function (pi: ExtensionAPI) {
|
|
17
|
-
|
|
17
|
+
// Coordinator is created per session and shared by tools and commands.
|
|
18
|
+
let coordinator: GraphifyCoordinator | null = null;
|
|
19
|
+
const getCoordinator = () => coordinator;
|
|
20
|
+
const registeredToolNames = new Set<string>();
|
|
18
21
|
|
|
19
|
-
// ── 1.
|
|
22
|
+
// ── 1. Register slash commands synchronously ─────────────────────
|
|
23
|
+
pi.registerCommand("graphify-status", {
|
|
24
|
+
description: "Show Graphify graph status for the current project",
|
|
25
|
+
handler: async (_args, ctx) => {
|
|
26
|
+
const current = getCoordinator();
|
|
27
|
+
if (!current) {
|
|
28
|
+
ctx.ui.notify("Graphify is not initialized.", "warning");
|
|
29
|
+
return;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
const status = await current.status({ cwd: ctx.cwd });
|
|
33
|
+
if (status.hasGraph && status.graphPath) {
|
|
34
|
+
ctx.ui.notify(`Graphify graph ready: ${status.graphPath}`, "info");
|
|
35
|
+
} else {
|
|
36
|
+
ctx.ui.notify("No Graphify graph found. Build one with `graphify .`.", "warning");
|
|
37
|
+
}
|
|
38
|
+
},
|
|
39
|
+
});
|
|
40
|
+
|
|
41
|
+
// ── 2. Initialize coordinator and register tools per session ───────
|
|
42
|
+
// Tools are registered inside session_start so we can gate them by the
|
|
43
|
+
// coordinator's detected capabilities. Registering at load time would mean
|
|
44
|
+
// the LLM sees tools that may not be supported by the installed Graphify version.
|
|
20
45
|
pi.on("session_start", async (_event, ctx) => {
|
|
21
|
-
|
|
46
|
+
coordinator = new GraphifyCoordinator({ cwd: ctx.cwd });
|
|
47
|
+
await coordinator.initialize();
|
|
48
|
+
|
|
49
|
+
registerGraphifyTools(pi, getCoordinator, { registeredToolNames });
|
|
50
|
+
|
|
51
|
+
const status = await coordinator.status({ cwd: ctx.cwd });
|
|
22
52
|
if (status.hasGraph && status.graphPath) {
|
|
23
53
|
ctx.ui.notify(`Graphify graph ready: ${status.graphPath}`, "info");
|
|
24
54
|
}
|
|
55
|
+
|
|
56
|
+
if (coordinator.version === null) {
|
|
57
|
+
ctx.ui.notify(
|
|
58
|
+
"Graphify CLI was not detected on PATH. Install Graphify to use build, query, and other graph tools.",
|
|
59
|
+
"warning",
|
|
60
|
+
);
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
for (const warning of coordinator.warnings) {
|
|
64
|
+
ctx.ui.notify(warning, "warning");
|
|
65
|
+
}
|
|
25
66
|
});
|
|
26
67
|
|
|
68
|
+
// ── 3. Inject context hint when a graph is present ────────────────
|
|
27
69
|
pi.on("before_agent_start", async (event, ctx) => {
|
|
28
|
-
const status =
|
|
70
|
+
const status = graphifyStatus(ctx.cwd);
|
|
29
71
|
if (!status.hasGraph || !status.graphPath) {
|
|
30
72
|
return;
|
|
31
73
|
}
|
|
@@ -36,19 +78,11 @@ export default function (pi: ExtensionAPI) {
|
|
|
36
78
|
};
|
|
37
79
|
});
|
|
38
80
|
|
|
39
|
-
// ──
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
handler: async (_args, ctx) => {
|
|
46
|
-
const status = await graphifyStatus(ctx.cwd);
|
|
47
|
-
if (status.hasGraph && status.graphPath) {
|
|
48
|
-
ctx.ui.notify(`Graphify graph ready: ${status.graphPath}`, "info");
|
|
49
|
-
} else {
|
|
50
|
-
ctx.ui.notify("No Graphify graph found. Run /graphify-build to create one.", "warning");
|
|
51
|
-
}
|
|
52
|
-
},
|
|
81
|
+
// ── 4. Clean up coordinator on shutdown ─────────────────────────
|
|
82
|
+
pi.on("session_shutdown", async () => {
|
|
83
|
+
if (coordinator) {
|
|
84
|
+
await coordinator.close();
|
|
85
|
+
coordinator = null;
|
|
86
|
+
}
|
|
53
87
|
});
|
|
54
88
|
}
|
package/package.json
CHANGED
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@wwjd/pi-graphify",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.3.0",
|
|
4
4
|
"description": "Graphify knowledge graph integration for Pi coding agent",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"license": "MIT",
|
|
7
|
-
"author": "
|
|
7
|
+
"author": "Wian <wianvdm4@gmail.com>",
|
|
8
8
|
"repository": {
|
|
9
9
|
"type": "git",
|
|
10
10
|
"url": "git+https://github.com/WianVDM/pi-graphify.git"
|
|
@@ -34,7 +34,7 @@
|
|
|
34
34
|
"access": "public"
|
|
35
35
|
},
|
|
36
36
|
"scripts": {
|
|
37
|
-
"test": "
|
|
37
|
+
"test": "node --test --import tsx src/tools/tools.test.ts src/extensions/extension-load.test.ts",
|
|
38
38
|
"lint": "biome check .",
|
|
39
39
|
"lint:fix": "biome check --write .",
|
|
40
40
|
"format": "biome format --write .",
|
|
@@ -57,6 +57,7 @@
|
|
|
57
57
|
"@earendil-works/pi-ai": "^0.74.0",
|
|
58
58
|
"@earendil-works/pi-coding-agent": "^0.80.6",
|
|
59
59
|
"@types/node": "^20.0.0",
|
|
60
|
+
"tsx": "^4.23.0",
|
|
60
61
|
"typebox": "^1.1.24",
|
|
61
62
|
"typescript": "^5.0.0"
|
|
62
63
|
},
|
|
@@ -0,0 +1,201 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* CLI backend for Graphify.
|
|
3
|
+
*
|
|
4
|
+
* Resolves the `graphify` executable, runs commands via `execFile`, and
|
|
5
|
+
* normalizes the results into the backend-agnostic `GraphifyResult` shape.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import { execFile } from "node:child_process";
|
|
9
|
+
import { existsSync } from "node:fs";
|
|
10
|
+
import { homedir } from "node:os";
|
|
11
|
+
import { join, resolve } from "node:path";
|
|
12
|
+
import { buildCapabilities, detectVersion } from "../version.js";
|
|
13
|
+
import type { GraphifyBackend, GraphifyCapabilities, GraphifyResult } from "./types.js";
|
|
14
|
+
import { allCapabilitiesDisabled } from "./types.js";
|
|
15
|
+
|
|
16
|
+
const DEFAULT_TIMEOUT = 30_000;
|
|
17
|
+
|
|
18
|
+
export interface CliBackendOptions {
|
|
19
|
+
cwd: string;
|
|
20
|
+
timeoutMs?: number;
|
|
21
|
+
/** Explicit executable path from config or env. */
|
|
22
|
+
explicitPath?: string;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
export class CliBackend implements GraphifyBackend {
|
|
26
|
+
readonly type = "cli" as const;
|
|
27
|
+
private readonly cwd: string;
|
|
28
|
+
private readonly timeoutMs: number;
|
|
29
|
+
private readonly explicitPath: string | undefined;
|
|
30
|
+
private _cli: string | null = null;
|
|
31
|
+
private _version: string | null = null;
|
|
32
|
+
private _capabilities: GraphifyCapabilities | null = null;
|
|
33
|
+
|
|
34
|
+
constructor(options: CliBackendOptions) {
|
|
35
|
+
this.cwd = options.cwd;
|
|
36
|
+
this.timeoutMs = options.timeoutMs ?? DEFAULT_TIMEOUT;
|
|
37
|
+
this.explicitPath = options.explicitPath;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
get version(): string | null {
|
|
41
|
+
return this._version;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
get capabilities(): GraphifyCapabilities {
|
|
45
|
+
return this._capabilities ?? allCapabilitiesDisabled();
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
async initialize(): Promise<void> {
|
|
49
|
+
this._cli = await this.resolveCli();
|
|
50
|
+
const versionInfo = await detectVersion(this._cli, this.cwd);
|
|
51
|
+
this._version = versionInfo.version;
|
|
52
|
+
|
|
53
|
+
this._capabilities = await buildCapabilities(this._cli, this.cwd, versionInfo, async (args) =>
|
|
54
|
+
this.runHelp(args),
|
|
55
|
+
);
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
async run(operation: string, options: unknown): Promise<GraphifyResult> {
|
|
59
|
+
const cli = await this.resolveCli();
|
|
60
|
+
const args = buildArgs(operation, options);
|
|
61
|
+
const start = performance.now();
|
|
62
|
+
|
|
63
|
+
return new Promise((resolve) => {
|
|
64
|
+
execFile(cli, args, { cwd: this.cwd, timeout: this.timeoutMs }, (error, stdout, stderr) => {
|
|
65
|
+
const durationMs = Math.round(performance.now() - start);
|
|
66
|
+
if (error) {
|
|
67
|
+
resolve({
|
|
68
|
+
stdout: stdout.trim(),
|
|
69
|
+
stderr: stderr.trim() || error.message,
|
|
70
|
+
exitCode: typeof error.code === "number" ? error.code : 1,
|
|
71
|
+
durationMs,
|
|
72
|
+
backend: "cli",
|
|
73
|
+
feature: operation,
|
|
74
|
+
});
|
|
75
|
+
return;
|
|
76
|
+
}
|
|
77
|
+
resolve({
|
|
78
|
+
stdout: stdout.trim(),
|
|
79
|
+
stderr: stderr.trim(),
|
|
80
|
+
exitCode: 0,
|
|
81
|
+
durationMs,
|
|
82
|
+
backend: "cli",
|
|
83
|
+
feature: operation,
|
|
84
|
+
});
|
|
85
|
+
});
|
|
86
|
+
});
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
async close(): Promise<void> {
|
|
90
|
+
// CLI backend has no persistent process to clean up.
|
|
91
|
+
this._cli = null;
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
private async resolveCli(): Promise<string> {
|
|
95
|
+
if (this._cli) return this._cli;
|
|
96
|
+
if (this.explicitPath) return this.explicitPath;
|
|
97
|
+
|
|
98
|
+
// Try the PATH first.
|
|
99
|
+
const fromPath = await findOnPath("graphify");
|
|
100
|
+
if (fromPath) return fromPath;
|
|
101
|
+
|
|
102
|
+
// Common install locations.
|
|
103
|
+
const candidates = [
|
|
104
|
+
join(homedir(), ".local", "bin", "graphify"),
|
|
105
|
+
join(homedir(), ".cargo", "bin", "graphify"),
|
|
106
|
+
join(homedir(), ".pipx", "venvs", "graphify", "bin", "graphify"),
|
|
107
|
+
];
|
|
108
|
+
|
|
109
|
+
for (const candidate of candidates) {
|
|
110
|
+
if (existsSync(candidate)) return candidate;
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
// Fall back to the bare command and let execFile report the error.
|
|
114
|
+
return "graphify";
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
private async runHelp(args: string[]): Promise<{ stdout: string; exitCode: number }> {
|
|
118
|
+
const cli = await this.resolveCli();
|
|
119
|
+
return new Promise((resolve) => {
|
|
120
|
+
execFile(cli, args, { cwd: this.cwd, timeout: 10_000 }, (error, stdout, _stderr) => {
|
|
121
|
+
if (error) {
|
|
122
|
+
resolve({
|
|
123
|
+
stdout: stdout.trim(),
|
|
124
|
+
exitCode: typeof error.code === "number" ? error.code : 1,
|
|
125
|
+
});
|
|
126
|
+
return;
|
|
127
|
+
}
|
|
128
|
+
resolve({ stdout: stdout.trim(), exitCode: 0 });
|
|
129
|
+
});
|
|
130
|
+
});
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
/** Best-effort find an executable on the user PATH. */
|
|
135
|
+
function findOnPath(command: string): Promise<string | null> {
|
|
136
|
+
const checkCmd = process.platform === "win32" ? "where" : "which";
|
|
137
|
+
const shell = process.platform === "win32" ? "cmd.exe" : undefined;
|
|
138
|
+
|
|
139
|
+
return new Promise((resolve) => {
|
|
140
|
+
execFile(checkCmd, [command], { shell }, (error, stdout) => {
|
|
141
|
+
if (error || !stdout.trim()) {
|
|
142
|
+
resolve(null);
|
|
143
|
+
return;
|
|
144
|
+
}
|
|
145
|
+
resolve(stdout.split("\n")[0].trim());
|
|
146
|
+
});
|
|
147
|
+
});
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
/** Build a CLI argument array from an operation and options. */
|
|
151
|
+
function buildArgs(operation: string, options: unknown): string[] {
|
|
152
|
+
if (!options || typeof options !== "object") {
|
|
153
|
+
return [operation];
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
const opts = options as Record<string, unknown>;
|
|
157
|
+
const args = [operation];
|
|
158
|
+
|
|
159
|
+
if (opts.cwd && typeof opts.cwd === "string") {
|
|
160
|
+
// graphify commands operate on cwd via the spawned process working directory;
|
|
161
|
+
// no explicit flag is needed for most operations.
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
if (opts.codeOnly === true) args.push("--code-only");
|
|
165
|
+
if (opts.update === true) args.push("--update");
|
|
166
|
+
if (opts.directed === true) args.push("--directed");
|
|
167
|
+
|
|
168
|
+
if (opts.question && typeof opts.question === "string") {
|
|
169
|
+
args.push(opts.question);
|
|
170
|
+
}
|
|
171
|
+
if (opts.source && typeof opts.source === "string") {
|
|
172
|
+
args.push("--source", opts.source);
|
|
173
|
+
}
|
|
174
|
+
if (opts.target && typeof opts.target === "string") {
|
|
175
|
+
args.push("--target", opts.target);
|
|
176
|
+
}
|
|
177
|
+
if (opts.node && typeof opts.node === "string") {
|
|
178
|
+
args.push("--node", opts.node);
|
|
179
|
+
}
|
|
180
|
+
if (Array.isArray(opts.files)) {
|
|
181
|
+
for (const file of opts.files) {
|
|
182
|
+
if (typeof file === "string") args.push(file);
|
|
183
|
+
}
|
|
184
|
+
}
|
|
185
|
+
if (Array.isArray(opts.paths)) {
|
|
186
|
+
for (const p of opts.paths) {
|
|
187
|
+
if (typeof p === "string") {
|
|
188
|
+
args.push(resolve(p));
|
|
189
|
+
}
|
|
190
|
+
}
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
return args;
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
/** Create a CLI backend and initialize it. */
|
|
197
|
+
export async function createCliBackend(options: CliBackendOptions): Promise<CliBackend> {
|
|
198
|
+
const backend = new CliBackend(options);
|
|
199
|
+
await backend.initialize();
|
|
200
|
+
return backend;
|
|
201
|
+
}
|