diff --git a/commands/gsd/graphify.md b/commands/gsd/graphify.md index 52ecacdf7..dcdb7cc8e 100644 --- a/commands/gsd/graphify.md +++ b/commands/gsd/graphify.md @@ -153,7 +153,7 @@ gsd-tools path: $HOME/.claude/get-shit-done/bin/gsd-tools.cjs 1. **Invoke graphify:** Run from the project root: ``` - graphify . --update + graphify update . ``` This builds the knowledge graph with SHA256 incremental caching. Timeout: up to 5 minutes (or as configured via graphify.build_timeout). diff --git a/get-shit-done/bin/lib/graphify.cjs b/get-shit-done/bin/lib/graphify.cjs index 5a7fa2a87..a31e0fa2f 100644 --- a/get-shit-done/bin/lib/graphify.cjs +++ b/get-shit-done/bin/lib/graphify.cjs @@ -102,26 +102,55 @@ function checkGraphifyInstalled() { } /** - * Detect graphify version via python3 importlib.metadata and check compatibility. + * Detect graphify version and check compatibility. * Tested range: >=0.4.0,<1.0 * + * Detection strategy: + * 1. Try `graphify --version` (works for most CLI installations, incl. venv installs) + * 2. Fall back to python3 importlib.metadata (legacy / system Python path) + * 3. Return null version gracefully if both fail + * * @returns {{ version: string|null, compatible: boolean|null, warning: string|null }} */ function checkGraphifyVersion() { - const result = childProcess.spawnSync('python3', [ - '-c', - 'from importlib.metadata import version; print(version("graphifyy"))', - ], { + // Strategy 1: try `graphify --version` directly (2s timeout -- fast path) + const versionResult = childProcess.spawnSync('graphify', ['--version'], { stdio: 'pipe', encoding: 'utf-8', - timeout: 5000, + timeout: 2000, }); - if (result.status !== 0 || !result.stdout || !result.stdout.trim()) { + let versionStr = null; + + if (!versionResult.error && versionResult.status === 0) { + const raw = (versionResult.stdout || '').trim(); + // graphify --version may emit "graphify 0.4.23" or just "0.4.23" + const match = raw.match(/(\d+\.\d+(?:\.\d+)*)/); + if (match) { + versionStr = match[1]; + } + } + + // Strategy 2: fall back to python3 importlib.metadata + if (!versionStr) { + const pyResult = childProcess.spawnSync('python3', [ + '-c', + 'from importlib.metadata import version; print(version("graphifyy"))', + ], { + stdio: 'pipe', + encoding: 'utf-8', + timeout: 5000, + }); + + if (!pyResult.error && pyResult.status === 0 && pyResult.stdout && pyResult.stdout.trim()) { + versionStr = pyResult.stdout.trim(); + } + } + + if (!versionStr) { return { version: null, compatible: null, warning: 'Could not determine graphify version' }; } - const versionStr = result.stdout.trim(); const parts = versionStr.split('.').map(Number); if (parts.length < 2 || parts.some(isNaN)) { diff --git a/tests/graphify.test.cjs b/tests/graphify.test.cjs index 988c7c0c8..afef9edb0 100644 --- a/tests/graphify.test.cjs +++ b/tests/graphify.test.cjs @@ -384,18 +384,36 @@ describe('checkGraphifyVersion', () => { assert.ok(result.warning.includes('Could not parse')); }); - test('calls python3 with importlib.metadata', () => { - let capturedCmd; - let capturedArgs; + test('tries graphify --version first before python3', () => { + const calls = []; mock.method(childProcess, 'spawnSync', (cmd, args) => { - capturedCmd = cmd; - capturedArgs = args; + calls.push({ cmd, args }); return { status: 0, stdout: '0.4.3\n', stderr: '', error: undefined, signal: null }; }); checkGraphifyVersion(); - assert.strictEqual(capturedCmd, 'python3'); - assert.ok(capturedArgs.some(arg => arg.includes('importlib.metadata'))); + assert.ok(calls.length >= 1, 'at least one spawnSync call'); + assert.strictEqual(calls[0].cmd, 'graphify'); + assert.deepStrictEqual(calls[0].args, ['--version']); + }); + + test('falls back to python3 importlib.metadata when graphify --version fails', () => { + const calls = []; + mock.method(childProcess, 'spawnSync', (cmd, args) => { + calls.push({ cmd, args }); + if (cmd === 'graphify') { + return { status: 1, stdout: '', stderr: 'unknown option', error: undefined, signal: null }; + } + // python3 fallback + return { status: 0, stdout: '0.4.3\n', stderr: '', error: undefined, signal: null }; + }); + + const result = checkGraphifyVersion(); + assert.strictEqual(result.version, '0.4.3'); + assert.strictEqual(result.compatible, true); + const pythonCall = calls.find(c => c.cmd === 'python3'); + assert.ok(pythonCall, 'python3 should be called as fallback'); + assert.ok(pythonCall.args.some(arg => arg.includes('importlib.metadata'))); }); });