@wwjd/pi-graphify 0.1.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.
@@ -0,0 +1,241 @@
1
+ # Versioning and Dependency Strategy: pi-graphify
2
+
3
+ This document describes how `pi-graphify` manages its dependency on the external Graphify CLI and how it handles version mismatches.
4
+
5
+ ---
6
+
7
+ ## Supported Graphify range
8
+
9
+ `pi-graphify` uses a **hybrid strategy** for determining the supported Graphify range:
10
+
11
+ 1. A **hardcoded minimum version** is enforced for safety. This protects the extension from known incompatible Graphify versions.
12
+ 2. **Runtime capability detection** is used to determine which features are actually available. This is more reliable than version number parsing alone.
13
+ 3. A **hardcoded maximum version** is only set when a known breaking Graphify release is discovered.
14
+
15
+ This approach balances maintainability with adaptability. Auto-detecting the full supported range from Graphify’s CLI output is too fragile because the CLI’s help text, flag names, and output format can change between releases.
16
+
17
+ ```typescript
18
+ const SUPPORTED_GRAPHIFY_RANGE = {
19
+ min: '2.100.0', // Hardcoded minimum tested version
20
+ recommended: 'latest', // Best experience
21
+ max: undefined, // No known breaking upper bound yet
22
+ };
23
+ ```
24
+
25
+ These values are examples. The exact `min` will be set during implementation based on real Graphify CLI behavior.
26
+
27
+ ---
28
+
29
+ ## Version detection
30
+
31
+ The extension detects the installed Graphify version in this order:
32
+
33
+ 1. **Environment variable** `GRAPHIFY_PATH` — if set, use this executable.
34
+ 2. **Config** `graphifyPath` — if set, use this executable.
35
+ 3. **PATH lookup** — run `which graphify` (Unix) or `where graphify` (Windows).
36
+ 4. **Common installation paths** — check `~/.local/bin/graphify`, `~/.cargo/bin/graphify`, pipx/uv tool directories.
37
+
38
+ Once an executable is found, the extension runs:
39
+
40
+ ```bash
41
+ graphify --version
42
+ ```
43
+
44
+ or falls back to:
45
+
46
+ ```bash
47
+ graphify version
48
+ ```
49
+
50
+ If neither works, the version is reported as `unknown` and the extension enters best-effort mode.
51
+
52
+ ---
53
+
54
+ ## Installation method detection
55
+
56
+ The extension attempts to detect how Graphify was installed so it can suggest the correct update command:
57
+
58
+ | Method | Detection | Update command |
59
+ |---|---|---|
60
+ | uv tool | `uv tool list` includes graphifyy | `uv tool install --upgrade graphifyy` |
61
+ | pipx | `pipx list` includes graphifyy | `pipx upgrade graphifyy` |
62
+ | pip user | Executable in pip user bin | `pip install --upgrade graphifyy` |
63
+ | pip system | Executable in system site | `pip install --upgrade graphifyy` or system package manager |
64
+ | cargo | Binary from `cargo install` | `cargo install fallow-cli` (if applicable) |
65
+ | unknown | Cannot determine | `pip install --upgrade graphifyy` or `uv tool install --upgrade graphifyy` |
66
+
67
+ The update command is shown to the user, never executed automatically.
68
+
69
+ ---
70
+
71
+ ## Version mismatch handling
72
+
73
+ ### Graphify missing
74
+
75
+ - Disable all Graphify tools.
76
+ - Show a notification with installation instructions.
77
+ - Keep the session alive. The extension is otherwise inert.
78
+
79
+ ### Version below minimum
80
+
81
+ - Disable features that require the minimum version.
82
+ - Show a warning with the detected version, supported range, and update command.
83
+ - Basic query/explain may still work if the CLI contract is compatible.
84
+
85
+ ### Version above supported range
86
+
87
+ - If the extension has a known max version and the installed version exceeds it, disable features with known breaking changes.
88
+ - Show a warning asking the user to update `pi-graphify` if a newer version supports it.
89
+ - Log the issue for debugging.
90
+
91
+ ### Version unknown
92
+
93
+ - Enter best-effort mode.
94
+ - Assume basic CLI compatibility.
95
+ - Do not claim to support advanced features.
96
+
97
+ ### Feature-specific detection
98
+
99
+ Some features are gated by capability checks rather than strict version checks. For example:
100
+
101
+ - If `graphify --help` does not list `affected`, the `graphify_affected` tool is disabled.
102
+ - If the MCP server does not expose `shortest_path`, the coordinator falls back to CLI for that operation.
103
+
104
+ This makes the extension resilient to partial CLI support.
105
+
106
+ ---
107
+
108
+ ## Runtime capability detection
109
+
110
+ In addition to the hardcoded minimum version, the extension probes the installed Graphify CLI for capabilities. This is more robust than relying solely on version numbers.
111
+
112
+ ### How capability detection works
113
+
114
+ 1. **Version check** — If the version is below `min`, disable advanced features and warn.
115
+ 2. **Help parsing** — Run `graphify --help` and parse the available subcommands.
116
+ 3. **Feature probing** — For borderline features, run a lightweight command (e.g., `graphify affected --help`) to confirm the feature exists.
117
+ 4. **MCP discovery** — If MCP is requested, attempt to start the server and list available tools.
118
+
119
+ ### Why not fully auto-detect the range?
120
+
121
+ Fully auto-detecting the supported version range from the CLI is fragile because:
122
+ - Help text and flag names change.
123
+ - A command may exist but behave differently than expected.
124
+ - Subtle breaking changes (e.g., output format) are not visible in `--help`.
125
+
126
+ The hardcoded minimum is a safety guard. The capability detection widens support within that guard.
127
+
128
+ ### Example
129
+
130
+ ```typescript
131
+ const capabilities: GraphifyCapabilities = {
132
+ query: semver.gte(version, '2.0.0'),
133
+ path: semver.gte(version, '2.0.0'),
134
+ affected: hasCommand('affected'), // runtime probe
135
+ reflect: hasCommand('reflect'), // runtime probe
136
+ mcp: canStartMcpServer(), // runtime probe
137
+ };
138
+ ```
139
+
140
+ If a feature probe fails, only that feature is disabled. The rest of the extension continues to work.
141
+
142
+ ---
143
+
144
+ ## Capability matrix
145
+
146
+ The extension maintains a mapping of Graphify versions to features. This is used as an initial filter before runtime probing.
147
+
148
+ | Feature | Min CLI version | Runtime probe | Notes |
149
+ |---|---|---|---|
150
+ | `build` | 2.0.0 | `graphify build --help` | Core command. |
151
+ | `query` | 2.0.0 | `graphify query --help` | Core command. |
152
+ | `path` | 2.0.0 | `graphify path --help` | Core command. |
153
+ | `explain` | 2.0.0 | `graphify explain --help` | Core command. |
154
+ | `affected` | 2.50.0 | `graphify affected --help` | Example threshold. |
155
+ | `reflect` | 2.70.0 | `graphify reflect --help` | Example threshold. |
156
+ | `mcp` | 2.90.0 | Start MCP server and list tools | MCP server support. |
157
+ | `--directed` | 2.20.0 | `graphify build --help` mentions `--directed` | Build flag. |
158
+ | `--update` | 2.10.0 | `graphify build --help` mentions `--update` | Incremental build. |
159
+ | `--watch` | 2.30.0 | `graphify watch --help` | File watcher. |
160
+
161
+ These versions are illustrative. The actual values will be validated against the real Graphify CLI during implementation.
162
+
163
+ ---
164
+
165
+ ## Update handling
166
+
167
+ ### What the extension does
168
+
169
+ - Detect installed version.
170
+ - Compare to supported range.
171
+ - Notify user if an update is recommended.
172
+ - Provide the exact update command.
173
+
174
+ ### What the extension does NOT do
175
+
176
+ - Auto-update Graphify.
177
+ - Install Graphify automatically (with one exception: it may suggest the install command).
178
+ - Modify the user’s system packages without explicit action.
179
+
180
+ ### User-initiated update
181
+
182
+ A user can run:
183
+
184
+ ```text
185
+ /graphify-update
186
+ ```
187
+
188
+ or the agent can call `graphify_update`. This tool runs the detected update command with the user’s confirmation. It is only available in trusted projects and may require elevated permissions depending on the installation method.
189
+
190
+ ### Extension self-update
191
+
192
+ `pi-graphify` itself is updated through `pi update npm:pi-graphify`, not through Graphify. The extension’s npm version is independent of the Graphify CLI version.
193
+
194
+ ---
195
+
196
+ ## Backwards compatibility
197
+
198
+ ### For `pi-graphify` users
199
+
200
+ - Minor and patch releases of `pi-graphify` maintain backward compatibility with existing config and tool schemas.
201
+ - Major releases may change tool names, parameters, or config options.
202
+ - The supported Graphify CLI range may be widened in minor/patch releases.
203
+
204
+ ### For Graphify CLI users
205
+
206
+ - `pi-graphify` supports a range of Graphify CLI versions.
207
+ - New Graphify CLI features may not be available until `pi-graphify` is updated to expose them.
208
+ - Breaking Graphify CLI changes may require a major or minor `pi-graphify` release.
209
+
210
+ ---
211
+
212
+ ## Release cadence
213
+
214
+ ### Graphify CLI releases
215
+
216
+ Graphify releases independently. The `pi-graphify` maintainers will:
217
+ - Test new Graphify versions.
218
+ - Update the capability matrix.
219
+ - Release a new `pi-graphify` version if needed.
220
+
221
+ ### pi-graphify releases
222
+
223
+ - Patch releases: bug fixes, doc updates, dependency updates.
224
+ - Minor releases: new tools/commands, new Graphify CLI support, non-breaking features.
225
+ - Major releases: breaking changes to public API, removal of tools, changes to default behavior.
226
+
227
+ ---
228
+
229
+ ## Telemetry and logging
230
+
231
+ Version mismatches and capability failures are logged at the `warning` level. No data is sent to remote servers. Logs are local to the Pi session and can be used to diagnose issues.
232
+
233
+ ---
234
+
235
+ ## Open questions
236
+
237
+ 1. Should the extension pin a maximum Graphify version to avoid surprises from upstream CLI changes?
238
+ 2. Should the extension bundle a known-good Graphify CLI version in the future?
239
+ 3. How should the extension handle Graphify installed from a Git branch or source build?
240
+
241
+ These will be resolved during implementation and testing.
@@ -0,0 +1,54 @@
1
+ /**
2
+ * pi-graphify — Extension entry point
3
+ *
4
+ * Brings Graphify knowledge graph capabilities into Pi sessions:
5
+ * - Detects existing graphify-out/graph.json in the project
6
+ * - Registers LLM-callable tools (graphify_status, graphify_query, ...)
7
+ * - Registers slash commands (/graphify-build, /graphify-status, ...)
8
+ * - Optionally watches source files for incremental rebuilds
9
+ */
10
+
11
+ import type { ExtensionAPI } from "@earendil-works/pi-coding-agent";
12
+ import { loadConfig } from "../src/config.js";
13
+ import { buildGraphifyHint, graphifyStatus } from "../src/graphify.js";
14
+ import { registerGraphifyTools } from "../src/tools/index.js";
15
+
16
+ export default function (pi: ExtensionAPI) {
17
+ const config = loadConfig();
18
+
19
+ // ── 1. Notify and inject context when a graph is present ──────────
20
+ pi.on("session_start", async (_event, ctx) => {
21
+ const status = await graphifyStatus(ctx.cwd);
22
+ if (status.hasGraph && status.graphPath) {
23
+ ctx.ui.notify(`Graphify graph ready: ${status.graphPath}`, "info");
24
+ }
25
+ });
26
+
27
+ pi.on("before_agent_start", async (event, ctx) => {
28
+ const status = await graphifyStatus(ctx.cwd);
29
+ if (!status.hasGraph || !status.graphPath) {
30
+ return;
31
+ }
32
+
33
+ const hint = buildGraphifyHint(status.graphPath);
34
+ return {
35
+ systemPrompt: `${event.systemPrompt}\n\n${hint}`,
36
+ };
37
+ });
38
+
39
+ // ── 2. Register custom tools the LLM can call ─────────────────────
40
+ registerGraphifyTools(pi, config);
41
+
42
+ // ── 3. Register slash commands ────────────────────────────────────
43
+ pi.registerCommand("graphify-status", {
44
+ description: "Show Graphify graph status for the current project",
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
+ },
53
+ });
54
+ }
package/package.json ADDED
@@ -0,0 +1,66 @@
1
+ {
2
+ "name": "@wwjd/pi-graphify",
3
+ "version": "0.1.0",
4
+ "description": "Graphify knowledge graph integration for Pi coding agent",
5
+ "type": "module",
6
+ "license": "MIT",
7
+ "author": "WIAN <WIANVDM4@GMAIL.COM>",
8
+ "repository": {
9
+ "type": "git",
10
+ "url": "git+https://github.com/WianVDM/pi-graphify.git"
11
+ },
12
+ "keywords": [
13
+ "pi-package",
14
+ "pi-extension",
15
+ "pi",
16
+ "pi-coding-agent",
17
+ "graphify",
18
+ "graphs",
19
+ "knowledge-graph",
20
+ "visualization"
21
+ ],
22
+ "files": [
23
+ "src",
24
+ "extensions",
25
+ "docs",
26
+ "README.md",
27
+ "CHANGELOG.md",
28
+ "RELEASE.md",
29
+ "AGENTS.md",
30
+ "LICENSE",
31
+ "!.github"
32
+ ],
33
+ "publishConfig": {
34
+ "access": "public"
35
+ },
36
+ "scripts": {
37
+ "test": "echo \"No tests yet\" && exit 0",
38
+ "lint": "biome check .",
39
+ "lint:fix": "biome check --write .",
40
+ "format": "biome format --write .",
41
+ "typecheck": "tsc --noEmit"
42
+ },
43
+ "pi": {
44
+ "extensions": [
45
+ "./extensions"
46
+ ]
47
+ },
48
+ "peerDependencies": {
49
+ "@earendil-works/pi-agent-core": "*",
50
+ "@earendil-works/pi-ai": "*",
51
+ "@earendil-works/pi-coding-agent": "*",
52
+ "@earendil-works/pi-tui": "*",
53
+ "typebox": "*"
54
+ },
55
+ "devDependencies": {
56
+ "@biomejs/biome": "^2.4.14",
57
+ "@earendil-works/pi-ai": "^0.74.0",
58
+ "@earendil-works/pi-coding-agent": "^0.80.6",
59
+ "@types/node": "^20.0.0",
60
+ "typebox": "^1.1.24",
61
+ "typescript": "^5.0.0"
62
+ },
63
+ "dependencies": {
64
+ "chokidar": "^3.6.0"
65
+ }
66
+ }
package/src/config.ts ADDED
@@ -0,0 +1,26 @@
1
+ /**
2
+ * pi-graphify configuration
3
+ */
4
+
5
+ export interface GraphifyConfig {
6
+ /** Whether to auto-watch source files and rebuild the graph. */
7
+ autoWatch: boolean;
8
+ /** Debounce window for file watcher events (ms). */
9
+ debounceMs: number;
10
+ /** Timeout for full graph builds (ms). */
11
+ buildTimeoutMs: number;
12
+ /** Timeout for graph queries (ms). */
13
+ queryTimeoutMs: number;
14
+ /** Timeout for short CLI calls like status checks (ms). */
15
+ shortTimeoutMs: number;
16
+ }
17
+
18
+ export function loadConfig(): GraphifyConfig {
19
+ return {
20
+ autoWatch: false,
21
+ debounceMs: 2000,
22
+ buildTimeoutMs: 300_000,
23
+ queryTimeoutMs: 30_000,
24
+ shortTimeoutMs: 10_000,
25
+ };
26
+ }
@@ -0,0 +1,98 @@
1
+ /**
2
+ * Graphify CLI helpers
3
+ */
4
+
5
+ import { execFile } from "node:child_process";
6
+ import { existsSync } from "node:fs";
7
+ import { join } from "node:path";
8
+ import type { GraphifyConfig } from "./config.js";
9
+
10
+ export interface GraphifyResult {
11
+ stdout: string;
12
+ stderr: string;
13
+ exitCode: number;
14
+ }
15
+
16
+ export interface GraphStatus {
17
+ hasGraph: boolean;
18
+ graphPath?: string;
19
+ error?: string;
20
+ }
21
+
22
+ /** Best-effort resolve of the graphify CLI executable name. */
23
+ export async function findGraphifyCli(): Promise<string> {
24
+ // Pi shells run with the user's PATH, so the bare command is usually enough.
25
+ // We try a platform-aware check first; if it fails, we fall back to "graphify".
26
+ const checkCmd = process.platform === "win32" ? "where" : "which";
27
+ const shell = process.platform === "win32" ? "cmd.exe" : undefined;
28
+
29
+ return new Promise((resolve) => {
30
+ execFile(checkCmd, ["graphify"], { shell }, (error, stdout) => {
31
+ if (error || !stdout.trim()) {
32
+ resolve("graphify");
33
+ return;
34
+ }
35
+ // Use the first match returned by where/which.
36
+ resolve(stdout.split("\n")[0].trim());
37
+ });
38
+ });
39
+ }
40
+
41
+ /** Run a graphify CLI command with a timeout. */
42
+ export async function runGraphify(
43
+ args: string[],
44
+ cwd: string,
45
+ timeoutMs: number,
46
+ ): Promise<GraphifyResult> {
47
+ const cli = await findGraphifyCli();
48
+
49
+ return new Promise((resolve) => {
50
+ execFile(cli, args, { cwd, timeout: timeoutMs }, (error, stdout, stderr) => {
51
+ if (error) {
52
+ resolve({
53
+ stdout: stdout.trim(),
54
+ stderr: stderr.trim() || error.message,
55
+ exitCode: typeof error.code === "number" ? error.code : 1,
56
+ });
57
+ return;
58
+ }
59
+ resolve({
60
+ stdout: stdout.trim(),
61
+ stderr: stderr.trim(),
62
+ exitCode: 0,
63
+ });
64
+ });
65
+ });
66
+ }
67
+
68
+ /** Check whether a graph exists for the given project directory. */
69
+ export async function graphifyStatus(cwd: string): Promise<GraphStatus> {
70
+ const graphPath = join(cwd, "graphify-out", "graph.json");
71
+
72
+ if (!existsSync(graphPath)) {
73
+ return { hasGraph: false };
74
+ }
75
+
76
+ return { hasGraph: true, graphPath };
77
+ }
78
+
79
+ /** Build a short system-prompt hint that the graph is available. */
80
+ export function buildGraphifyHint(graphPath: string): string {
81
+ return [
82
+ "<graphify-context>",
83
+ `A Graphify knowledge graph is available at ${graphPath}.`,
84
+ "When answering questions about this codebase, prefer querying the graph via the graphify_status, graphify_query, graphify_path, and graphify_explain tools instead of grepping or reading raw files.",
85
+ "Only fall back to raw file reads when the graph lacks the needed detail or when modifying/debugging specific lines.",
86
+ "</graphify-context>",
87
+ ].join("\n");
88
+ }
89
+
90
+ /** Format a graphify CLI result for display. */
91
+ export function formatResult(result: GraphifyResult): string {
92
+ if (result.exitCode === 0) {
93
+ return result.stdout;
94
+ }
95
+ return `graphify exited with code ${result.exitCode}\n${result.stderr || result.stdout}`.trim();
96
+ }
97
+
98
+ export type { GraphifyConfig };
@@ -0,0 +1,11 @@
1
+ /**
2
+ * Tool registration barrel
3
+ */
4
+
5
+ import type { ExtensionAPI } from "@earendil-works/pi-coding-agent";
6
+ import type { GraphifyConfig } from "../config.js";
7
+ import { registerStatusTool } from "./status.js";
8
+
9
+ export function registerGraphifyTools(pi: ExtensionAPI, config: GraphifyConfig): void {
10
+ registerStatusTool(pi, config);
11
+ }
@@ -0,0 +1,43 @@
1
+ /**
2
+ * Graphify status tool
3
+ */
4
+
5
+ import type { ExtensionAPI } from "@earendil-works/pi-coding-agent";
6
+ import { Type } from "typebox";
7
+ import type { GraphifyConfig } from "../config.js";
8
+ import { graphifyStatus } from "../graphify.js";
9
+
10
+ export function registerStatusTool(pi: ExtensionAPI, _config: GraphifyConfig): void {
11
+ pi.registerTool({
12
+ name: "graphify_status",
13
+ label: "Graphify Status",
14
+ description:
15
+ "Check whether a Graphify knowledge graph exists for the current project and whether the graphify CLI is available.",
16
+ parameters: Type.Object({}),
17
+ async execute(_toolCallId, _params, _signal, _onUpdate, ctx) {
18
+ const status = await graphifyStatus(ctx.cwd);
19
+
20
+ if (!status.hasGraph) {
21
+ return {
22
+ content: [
23
+ {
24
+ type: "text",
25
+ text: "No Graphify graph found. Run /graphify-build to build one.",
26
+ },
27
+ ],
28
+ details: {},
29
+ };
30
+ }
31
+
32
+ return {
33
+ content: [
34
+ {
35
+ type: "text",
36
+ text: `Graphify graph is available at ${status.graphPath}.`,
37
+ },
38
+ ],
39
+ details: {},
40
+ };
41
+ },
42
+ });
43
+ }