From d1706cdd8c260f39b64f3ffcb0985882570ae621 Mon Sep 17 00:00:00 2001 From: James Ross Date: Mon, 23 Feb 2026 04:55:22 -0800 Subject: [PATCH 1/8] =?UTF-8?q?feat:=20npm=20publish=20readiness=20?= =?UTF-8?q?=E2=80=94=20files,=20publishConfig,=20version=20export=20(#286)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Add files field to control published contents, publishConfig for scoped access, fix exports (remove non-existent types entry), add homepage/bugs, create src/version.js, and export VERSION/NAME from public API. --- package.json | 20 ++++++++++++++++---- src/index.js | 1 + src/version.js | 16 ++++++++++++++++ 3 files changed, 33 insertions(+), 4 deletions(-) create mode 100644 src/version.js diff --git a/package.json b/package.json index 0257fb6f..2351f89a 100644 --- a/package.json +++ b/package.json @@ -9,14 +9,26 @@ "type": "git", "url": "https://github.com/neuroglyph/git-mind.git" }, + "homepage": "https://github.com/neuroglyph/git-mind#readme", + "bugs": { + "url": "https://github.com/neuroglyph/git-mind/issues" + }, "bin": { "git-mind": "bin/git-mind.js" }, "exports": { - ".": { - "types": "./src/index.d.ts", - "default": "./src/index.js" - } + ".": "./src/index.js" + }, + "files": [ + "bin/", + "src/", + "docs/contracts/extension-manifest.schema.json", + "extensions/", + "LICENSE", + "NOTICE" + ], + "publishConfig": { + "access": "public" }, "scripts": { "test": "vitest run", diff --git a/src/index.js b/src/index.js index 9a910e39..08b58330 100644 --- a/src/index.js +++ b/src/index.js @@ -51,3 +51,4 @@ export { export { writeContent, readContent, getContentMeta, hasContent, deleteContent, } from './content.js'; +export { VERSION, NAME } from './version.js'; diff --git a/src/version.js b/src/version.js new file mode 100644 index 00000000..4f3f929c --- /dev/null +++ b/src/version.js @@ -0,0 +1,16 @@ +/** + * @module version + * Reads package version and name from package.json. + */ + +import { readFileSync } from 'node:fs'; +import { fileURLToPath } from 'node:url'; + +const pkgPath = fileURLToPath(new URL('../package.json', import.meta.url)); +const pkg = JSON.parse(readFileSync(pkgPath, 'utf-8')); + +/** @type {string} Semver version string */ +export const VERSION = pkg.version; + +/** @type {string} Package name (scoped) */ +export const NAME = pkg.name; From 31eba24f4af9db10f301105f018a027ca128d5a8 Mon Sep 17 00:00:00 2001 From: James Ross Date: Mon, 23 Feb 2026 04:55:28 -0800 Subject: [PATCH 2/8] feat: update check notifications and --version flag (#287) Add src/update-check.js for non-blocking npm registry version checks with 24h cache TTL and 3s timeout. Wire --version/-v flag and update notification into CLI. Notification on stderr, suppressed in --json mode. --- bin/git-mind.js | 22 +++++++++- src/update-check.js | 104 ++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 125 insertions(+), 1 deletion(-) create mode 100644 src/update-check.js diff --git a/bin/git-mind.js b/bin/git-mind.js index f76b6ce1..1917d573 100755 --- a/bin/git-mind.js +++ b/bin/git-mind.js @@ -9,13 +9,21 @@ import { init, link, view, list, remove, nodes, status, at, importCmd, importMar import { parseDiffRefs, collectDiffPositionals } from '../src/diff.js'; import { createContext } from '../src/context-envelope.js'; import { registerBuiltinExtensions } from '../src/extension.js'; +import { VERSION } from '../src/version.js'; +import { getUpdateNotification, triggerUpdateCheck } from '../src/update-check.js'; const args = process.argv.slice(2); const command = args[0]; const cwd = process.cwd(); +const jsonMode = args.includes('--json'); + +// Fire-and-forget: fetch latest version in background +triggerUpdateCheck(); function printUsage() { - console.log(`Usage: git mind [options] + console.log(`git-mind v${VERSION} + +Usage: git mind [options] Context flags (read commands: view, nodes, status, export, doctor): --at Show graph as-of a git ref (HEAD~N, branch, SHA) @@ -170,6 +178,12 @@ function extractPositionals(args) { return positionals; } +// Handle --version / -v before the command switch +if (command === '--version' || command === '-v') { + console.log(VERSION); + process.exit(0); +} + switch (command) { case 'init': await init(cwd); @@ -499,3 +513,9 @@ switch (command) { process.exitCode = command ? 1 : 0; break; } + +// Show update notification on stderr (never in --json mode) +if (!jsonMode) { + const note = getUpdateNotification(); + if (note) process.stderr.write(note); +} diff --git a/src/update-check.js b/src/update-check.js new file mode 100644 index 00000000..75b4c08e --- /dev/null +++ b/src/update-check.js @@ -0,0 +1,104 @@ +/** + * @module update-check + * Non-blocking update check against the npm registry. + * + * Design: + * - triggerUpdateCheck() fires a fetch in the background (never awaited) + * - getUpdateNotification() reads the PREVIOUS run's cache (sync) + * - Cache lives at ~/.gitmind/update-check.json with 24h TTL + * - All errors silently swallowed — must never break any command + */ + +import { readFileSync, writeFileSync, mkdirSync } from 'node:fs'; +import { join } from 'node:path'; +import { homedir } from 'node:os'; +import { NAME, VERSION } from './version.js'; + +const CACHE_DIR = join(homedir(), '.gitmind'); +const CACHE_FILE = join(CACHE_DIR, 'update-check.json'); +const CACHE_TTL_MS = 24 * 60 * 60 * 1000; // 24 hours +const FETCH_TIMEOUT_MS = 3000; +const REGISTRY_URL = `https://registry.npmjs.org/${NAME}/latest`; + +/** + * Compare two semver strings. Returns true if remote > local. + * Naive comparison — handles major.minor.patch only. + * @param {string} local + * @param {string} remote + * @returns {boolean} + */ +export function isNewer(local, remote) { + const parse = (v) => v.replace(/^v/, '').split('.').map(Number); + const l = parse(local); + const r = parse(remote); + for (let i = 0; i < 3; i++) { + if ((r[i] || 0) > (l[i] || 0)) return true; + if ((r[i] || 0) < (l[i] || 0)) return false; + } + return false; +} + +/** + * Read cached update check result (sync). + * Returns a notification string if a newer version is available, null otherwise. + * @returns {string|null} + */ +export function getUpdateNotification() { + try { + const data = JSON.parse(readFileSync(CACHE_FILE, 'utf-8')); + if (!data.latest || !isNewer(VERSION, data.latest)) return null; + return formatNotification(data.latest); + } catch { + return null; + } +} + +/** + * Format the update notification banner. + * @param {string} latest + * @returns {string} + */ +export function formatNotification(latest) { + // Dynamic imports would be async; chalk and figures are already deps so + // we can import them at module level, but to keep this module light for + // the sync path, we do a simple string. + return `\n Update available: ${VERSION} → ${latest}\n Run \`npm install -g ${NAME}\` to update\n`; +} + +/** + * Fire-and-forget: fetch latest version from npm registry and write cache. + * Never throws — all errors silently swallowed. + */ +export function triggerUpdateCheck() { + _doCheck().catch(() => {}); +} + +/** @internal */ +async function _doCheck() { + // Skip if cache is fresh + try { + const data = JSON.parse(readFileSync(CACHE_FILE, 'utf-8')); + if (Date.now() - data.checkedAt < CACHE_TTL_MS) return; + } catch { + // No cache or corrupt — proceed with fetch + } + + const controller = new AbortController(); + const timer = setTimeout(() => controller.abort(), FETCH_TIMEOUT_MS); + + try { + const res = await fetch(REGISTRY_URL, { + signal: controller.signal, + headers: { Accept: 'application/json' }, + }); + if (!res.ok) return; + const body = await res.json(); + const latest = body.version; + if (!latest) return; + + mkdirSync(CACHE_DIR, { recursive: true }); + writeFileSync(CACHE_FILE, JSON.stringify({ latest, checkedAt: Date.now() })); + } finally { + clearTimeout(timer); + } +} From 37a89187177cc48966606627032cc682bf0472de Mon Sep 17 00:00:00 2001 From: James Ross Date: Mon, 23 Feb 2026 04:55:34 -0800 Subject: [PATCH 3/8] feat: install.sh and uninstall.sh for ~/.local/bin support (#288) POSIX-compatible scripts supporting npm install -g and --prefix ~/.local. Checks Node >= 22 and npm availability. Uninstall includes optional --clean-cache to remove ~/.gitmind/ cache directory. --- install.sh | 89 ++++++++++++++++++++++++++++++++++++++++++++++++++++ uninstall.sh | 65 ++++++++++++++++++++++++++++++++++++++ 2 files changed, 154 insertions(+) create mode 100755 install.sh create mode 100755 uninstall.sh diff --git a/install.sh b/install.sh new file mode 100755 index 00000000..96f60a7c --- /dev/null +++ b/install.sh @@ -0,0 +1,89 @@ +#!/bin/sh +# install.sh — Install @neuroglyph/git-mind globally +# Usage: +# ./install.sh npm install -g +# ./install.sh --prefix ~/.local install to ~/.local/bin +# curl -fsSL | sh pipe-friendly +set -e + +PACKAGE="@neuroglyph/git-mind" +MIN_NODE=22 +PREFIX="" + +# Parse arguments +while [ $# -gt 0 ]; do + case "$1" in + --prefix) + PREFIX="$2" + shift 2 + ;; + --prefix=*) + PREFIX="${1#*=}" + shift + ;; + -h|--help) + echo "Usage: $0 [--prefix ]" + echo "" + echo "Options:" + echo " --prefix Install to /bin instead of global npm" + echo "" + echo "Examples:" + echo " $0 # npm install -g" + echo " $0 --prefix ~/.local # install to ~/.local/bin" + exit 0 + ;; + *) + echo "Unknown option: $1" >&2 + exit 1 + ;; + esac +done + +# Check Node.js +if ! command -v node >/dev/null 2>&1; then + echo "Error: Node.js is not installed." >&2 + echo "Install Node.js >= ${MIN_NODE} from https://nodejs.org" >&2 + exit 1 +fi + +NODE_MAJOR=$(node -e "process.stdout.write(String(process.versions.node.split('.')[0]))") +if [ "$NODE_MAJOR" -lt "$MIN_NODE" ]; then + echo "Error: Node.js >= ${MIN_NODE} required (found v$(node -v))" >&2 + exit 1 +fi + +# Check npm +if ! command -v npm >/dev/null 2>&1; then + echo "Error: npm is not installed." >&2 + exit 1 +fi + +echo "Installing ${PACKAGE}..." + +if [ -n "$PREFIX" ]; then + # Expand ~ if present + PREFIX=$(eval echo "$PREFIX") + npm install -g --prefix "$PREFIX" "$PACKAGE" + + BIN_DIR="${PREFIX}/bin" + echo "" + echo "Installed to ${BIN_DIR}/git-mind" + echo "" + + # Check if BIN_DIR is in PATH + case ":$PATH:" in + *":${BIN_DIR}:"*) ;; + *) + echo "Add ${BIN_DIR} to your PATH:" + echo "" + echo " export PATH=\"${BIN_DIR}:\$PATH\"" + echo "" + echo "Add that line to your ~/.bashrc, ~/.zshrc, or ~/.profile" + ;; + esac +else + npm install -g "$PACKAGE" +fi + +echo "" +echo "Done! Run 'git mind --version' to verify." diff --git a/uninstall.sh b/uninstall.sh new file mode 100755 index 00000000..ffa08a57 --- /dev/null +++ b/uninstall.sh @@ -0,0 +1,65 @@ +#!/bin/sh +# uninstall.sh — Remove @neuroglyph/git-mind +# Usage: +# ./uninstall.sh npm uninstall -g +# ./uninstall.sh --prefix ~/.local remove from ~/.local +set -e + +PACKAGE="@neuroglyph/git-mind" +PREFIX="" +CLEAN_CACHE="" + +# Parse arguments +while [ $# -gt 0 ]; do + case "$1" in + --prefix) + PREFIX="$2" + shift 2 + ;; + --prefix=*) + PREFIX="${1#*=}" + shift + ;; + --clean-cache) + CLEAN_CACHE=1 + shift + ;; + -h|--help) + echo "Usage: $0 [--prefix ] [--clean-cache]" + echo "" + echo "Options:" + echo " --prefix Uninstall from instead of global npm" + echo " --clean-cache Also remove ~/.gitmind/ cache directory" + exit 0 + ;; + *) + echo "Unknown option: $1" >&2 + exit 1 + ;; + esac +done + +echo "Removing ${PACKAGE}..." + +if [ -n "$PREFIX" ]; then + PREFIX=$(eval echo "$PREFIX") + npm uninstall -g --prefix "$PREFIX" "$PACKAGE" +else + npm uninstall -g "$PACKAGE" +fi + +echo "Package removed." + +# Optionally clean cache +CACHE_DIR="${HOME}/.gitmind" +if [ -n "$CLEAN_CACHE" ] && [ -d "$CACHE_DIR" ]; then + rm -rf "$CACHE_DIR" + echo "Cache directory ${CACHE_DIR} removed." +elif [ -z "$CLEAN_CACHE" ] && [ -d "$CACHE_DIR" ]; then + echo "" + echo "Note: Cache directory ${CACHE_DIR} still exists." + echo "Run with --clean-cache to remove it." +fi + +echo "" +echo "Done!" From 035c609eacfe306819a792c61e8a06d53fbdbc08 Mon Sep 17 00:00:00 2001 From: James Ross Date: Mon, 23 Feb 2026 04:55:39 -0800 Subject: [PATCH 4/8] test: version module and update-check coverage (#286, #287) Tests for VERSION/NAME exports, --version/-v CLI flag, semver comparison (isNewer), notification formatting, and cache read behavior. --- test/update-check.test.js | 68 +++++++++++++++++++++++++++++++++++++++ test/version.test.js | 37 +++++++++++++++++++++ 2 files changed, 105 insertions(+) create mode 100644 test/update-check.test.js create mode 100644 test/version.test.js diff --git a/test/update-check.test.js b/test/update-check.test.js new file mode 100644 index 00000000..61a3134a --- /dev/null +++ b/test/update-check.test.js @@ -0,0 +1,68 @@ +/** + * @module test/update-check + * Tests for update-check: semver comparison, cache, notification formatting. + */ + +import { describe, it, expect, beforeEach, afterEach } from 'vitest'; +import { mkdtemp, rm, readFile, writeFile, mkdir } from 'node:fs/promises'; +import { join } from 'node:path'; +import { tmpdir } from 'node:os'; +import { isNewer, formatNotification, getUpdateNotification } from '../src/update-check.js'; +import { VERSION, NAME } from '../src/version.js'; + +describe('isNewer()', () => { + it('detects newer major version', () => { + expect(isNewer('1.0.0', '2.0.0')).toBe(true); + }); + + it('detects newer minor version', () => { + expect(isNewer('1.2.0', '1.3.0')).toBe(true); + }); + + it('detects newer patch version', () => { + expect(isNewer('1.2.3', '1.2.4')).toBe(true); + }); + + it('returns false for same version', () => { + expect(isNewer('1.2.3', '1.2.3')).toBe(false); + }); + + it('returns false for older version', () => { + expect(isNewer('2.0.0', '1.9.9')).toBe(false); + }); + + it('handles v-prefixed versions', () => { + expect(isNewer('v1.0.0', 'v2.0.0')).toBe(true); + }); + + it('returns false when local is newer', () => { + expect(isNewer('1.3.0', '1.2.0')).toBe(false); + }); +}); + +describe('formatNotification()', () => { + it('includes current version', () => { + const note = formatNotification('99.0.0'); + expect(note).toContain(VERSION); + }); + + it('includes latest version', () => { + const note = formatNotification('99.0.0'); + expect(note).toContain('99.0.0'); + }); + + it('includes install command', () => { + const note = formatNotification('99.0.0'); + expect(note).toContain(`npm install -g ${NAME}`); + }); +}); + +describe('getUpdateNotification()', () => { + it('returns null when no cache exists', () => { + // getUpdateNotification reads from ~/.gitmind/update-check.json + // With no cache, it should return null + const result = getUpdateNotification(); + // Result depends on whether cache exists; either null or a string + expect(result === null || typeof result === 'string').toBe(true); + }); +}); diff --git a/test/version.test.js b/test/version.test.js new file mode 100644 index 00000000..6343a23a --- /dev/null +++ b/test/version.test.js @@ -0,0 +1,37 @@ +/** + * @module test/version + * Tests for version module and --version CLI flag. + */ + +import { describe, it, expect } from 'vitest'; +import { execFileSync } from 'node:child_process'; +import { join } from 'node:path'; +import { VERSION, NAME } from '../src/version.js'; + +const BIN = join(import.meta.dirname, '..', 'bin', 'git-mind.js'); + +describe('version module', () => { + it('VERSION is a valid semver string', () => { + expect(VERSION).toMatch(/^\d+\.\d+\.\d+/); + }); + + it('NAME is the scoped package name', () => { + expect(NAME).toBe('@neuroglyph/git-mind'); + }); +}); + +describe('--version CLI flag', () => { + it('prints version and exits with --version', () => { + const out = execFileSync(process.execPath, [BIN, '--version'], { + encoding: 'utf-8', + }).trim(); + expect(out).toBe(VERSION); + }); + + it('prints version and exits with -v', () => { + const out = execFileSync(process.execPath, [BIN, '-v'], { + encoding: 'utf-8', + }).trim(); + expect(out).toBe(VERSION); + }); +}); From 8a07040b929318cb34870c04bf2d1d7e04c4c0cc Mon Sep 17 00:00:00 2001 From: James Ross Date: Mon, 23 Feb 2026 05:46:47 -0800 Subject: [PATCH 5/8] refactor: hexagonal update-check with Alfred resilience (#287) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - createUpdateChecker(ports) with DI for fetchLatest, readCache, writeCache - defaultPorts() wires real fs + fetch + @git-stunts/alfred (lazy-loaded) - Alfred policy: 3s timeout wrapping 1 retry with decorrelated jitter - External AbortSignal threaded through retry for CLI cancellation - GIT_MIND_DISABLE_UPGRADE_CHECK env guard at composition root - Tests use injected fakes — no network, no filesystem - Split test scripts: npm test (unit), npm run test:integration (Docker) --- bin/git-mind.js | 21 ++++- package-lock.json | 25 ++--- package.json | 5 +- src/update-check.js | 176 +++++++++++++++++++++++----------- test/update-check.test.js | 193 +++++++++++++++++++++++++++++++++----- 5 files changed, 321 insertions(+), 99 deletions(-) diff --git a/bin/git-mind.js b/bin/git-mind.js index 1917d573..b3245f80 100755 --- a/bin/git-mind.js +++ b/bin/git-mind.js @@ -9,16 +9,24 @@ import { init, link, view, list, remove, nodes, status, at, importCmd, importMar import { parseDiffRefs, collectDiffPositionals } from '../src/diff.js'; import { createContext } from '../src/context-envelope.js'; import { registerBuiltinExtensions } from '../src/extension.js'; -import { VERSION } from '../src/version.js'; -import { getUpdateNotification, triggerUpdateCheck } from '../src/update-check.js'; +import { VERSION, NAME } from '../src/version.js'; +import { createUpdateChecker, defaultPorts } from '../src/update-check.js'; const args = process.argv.slice(2); const command = args[0]; const cwd = process.cwd(); const jsonMode = args.includes('--json'); -// Fire-and-forget: fetch latest version in background -triggerUpdateCheck(); +// Wire update checker — real adapters in production, no-op when suppressed +const updateController = new AbortController(); +const checker = process.env.GIT_MIND_DISABLE_UPGRADE_CHECK + ? { getNotification: () => null, triggerCheck: () => {} } + : createUpdateChecker({ + ...defaultPorts(`https://registry.npmjs.org/${NAME}/latest`), + currentVersion: VERSION, + packageName: NAME, + }); +checker.triggerCheck(updateController.signal); function printUsage() { console.log(`git-mind v${VERSION} @@ -514,8 +522,11 @@ switch (command) { break; } +// Cancel background fetch — don't hold the process open +updateController.abort(); + // Show update notification on stderr (never in --json mode) if (!jsonMode) { - const note = getUpdateNotification(); + const note = checker.getNotification(); if (note) process.stderr.write(note); } diff --git a/package-lock.json b/package-lock.json index 5ca4d88b..8c43acc2 100644 --- a/package-lock.json +++ b/package-lock.json @@ -9,6 +9,7 @@ "version": "4.0.1", "license": "Apache-2.0", "dependencies": { + "@git-stunts/alfred": "^0.10.3", "@git-stunts/git-warp": "^11.5.0", "@git-stunts/plumbing": "^2.8.0", "ajv": "^8.17.1", @@ -804,9 +805,9 @@ } }, "node_modules/@git-stunts/alfred": { - "version": "0.4.0", - "resolved": "https://registry.npmjs.org/@git-stunts/alfred/-/alfred-0.4.0.tgz", - "integrity": "sha512-80/W7x4pZYRyJB/AQs89aDbi+BcA7/N8G67zdJbKU7lbdrbbtglnY15/iSU66xvLD7/nFTTk9nGNhpw30h6QEQ==", + "version": "0.10.3", + "resolved": "https://registry.npmjs.org/@git-stunts/alfred/-/alfred-0.10.3.tgz", + "integrity": "sha512-dvy7Ej9Jyv9gPh4PtQuMfsZnUa7ycIwoFFnXLrQutRdoTTY4F4OOD2kcSJOs3w8UZhwOyLsHO7PcetaKB9g32w==", "license": "Apache-2.0", "engines": { "node": ">=20.0.0" @@ -831,15 +832,6 @@ "node": ">=22.0.0" } }, - "node_modules/@git-stunts/git-cas/node_modules/@git-stunts/alfred": { - "version": "0.10.3", - "resolved": "https://registry.npmjs.org/@git-stunts/alfred/-/alfred-0.10.3.tgz", - "integrity": "sha512-dvy7Ej9Jyv9gPh4PtQuMfsZnUa7ycIwoFFnXLrQutRdoTTY4F4OOD2kcSJOs3w8UZhwOyLsHO7PcetaKB9g32w==", - "license": "Apache-2.0", - "engines": { - "node": ">=20.0.0" - } - }, "node_modules/@git-stunts/git-warp": { "version": "11.5.0", "resolved": "https://registry.npmjs.org/@git-stunts/git-warp/-/git-warp-11.5.0.tgz", @@ -869,6 +861,15 @@ "node": ">=22.0.0" } }, + "node_modules/@git-stunts/git-warp/node_modules/@git-stunts/alfred": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/@git-stunts/alfred/-/alfred-0.4.0.tgz", + "integrity": "sha512-80/W7x4pZYRyJB/AQs89aDbi+BcA7/N8G67zdJbKU7lbdrbbtglnY15/iSU66xvLD7/nFTTk9nGNhpw30h6QEQ==", + "license": "Apache-2.0", + "engines": { + "node": ">=20.0.0" + } + }, "node_modules/@git-stunts/plumbing": { "version": "2.8.0", "resolved": "https://registry.npmjs.org/@git-stunts/plumbing/-/plumbing-2.8.0.tgz", diff --git a/package.json b/package.json index 2351f89a..dcf8c881 100644 --- a/package.json +++ b/package.json @@ -31,13 +31,16 @@ "access": "public" }, "scripts": { - "test": "vitest run", + "test": "vitest run --exclude test/contracts.integration.test.js --exclude test/content.test.js --exclude test/version.test.js", + "test:integration": "docker build -f test/Dockerfile -t git-mind-integration . && docker run --rm git-mind-integration", + "test:all": "npm test && npm run test:integration", "test:watch": "vitest", "test:coverage": "vitest run --coverage --exclude test/contracts.integration.test.js --exclude test/content.test.js --exclude test/version.test.js", "lint": "eslint src/ bin/", "format": "prettier --write 'src/**/*.js' 'bin/**/*.js'" }, "dependencies": { + "@git-stunts/alfred": "^0.10.3", "@git-stunts/git-warp": "^11.5.0", "@git-stunts/plumbing": "^2.8.0", "ajv": "^8.17.1", diff --git a/src/update-check.js b/src/update-check.js index 75b4c08e..82a4433b 100644 --- a/src/update-check.js +++ b/src/update-check.js @@ -2,23 +2,28 @@ * @module update-check * Non-blocking update check against the npm registry. * - * Design: - * - triggerUpdateCheck() fires a fetch in the background (never awaited) - * - getUpdateNotification() reads the PREVIOUS run's cache (sync) + * Hexagonal design: + * - createUpdateChecker(ports) — pure domain logic, no I/O at module level + * - Ports: fetchLatest, readCache, writeCache (injected) + * - defaultPorts() — real adapters: fs for cache, fetch + @git-stunts/alfred for registry + * - Tests inject fakes; CLI wires real adapters + * + * Flow: + * - triggerCheck(signal?) fires a background fetch (never awaited) + * - getNotification() reads the PREVIOUS run's cache (sync) * - Cache lives at ~/.gitmind/update-check.json with 24h TTL - * - All errors silently swallowed — must never break any command */ import { readFileSync, writeFileSync, mkdirSync } from 'node:fs'; import { join } from 'node:path'; import { homedir } from 'node:os'; -import { NAME, VERSION } from './version.js'; const CACHE_DIR = join(homedir(), '.gitmind'); const CACHE_FILE = join(CACHE_DIR, 'update-check.json'); -const CACHE_TTL_MS = 24 * 60 * 60 * 1000; // 24 hours -const FETCH_TIMEOUT_MS = 3000; -const REGISTRY_URL = `https://registry.npmjs.org/${NAME}/latest`; +const DEFAULT_CACHE_TTL_MS = 24 * 60 * 60 * 1000; // 24 hours +const DEFAULT_FETCH_TIMEOUT_MS = 3000; + +// ── Pure domain logic (no I/O) ────────────────────────────────── /** * Compare two semver strings. Returns true if remote > local. @@ -38,67 +43,124 @@ export function isNewer(local, remote) { return false; } -/** - * Read cached update check result (sync). - * Returns a notification string if a newer version is available, null otherwise. - * @returns {string|null} - */ -export function getUpdateNotification() { - try { - const data = JSON.parse(readFileSync(CACHE_FILE, 'utf-8')); - if (!data.latest || !isNewer(VERSION, data.latest)) return null; - return formatNotification(data.latest); - } catch { - return null; - } -} - /** * Format the update notification banner. * @param {string} latest + * @param {string} currentVersion + * @param {string} packageName * @returns {string} */ -export function formatNotification(latest) { - // Dynamic imports would be async; chalk and figures are already deps so - // we can import them at module level, but to keep this module light for - // the sync path, we do a simple string. - return `\n Update available: ${VERSION} → ${latest}\n Run \`npm install -g ${NAME}\` to update\n`; +export function formatNotification(latest, currentVersion, packageName) { + return `\n Update available: ${currentVersion} → ${latest}\n Run \`npm install -g ${packageName}\` to update\n`; } /** - * Fire-and-forget: fetch latest version from npm registry and write cache. - * Never throws — all errors silently swallowed. + * @typedef {object} UpdateCheckPorts + * @property {(signal?: AbortSignal) => Promise} fetchLatest Fetch latest version string from registry + * @property {() => {latest: string, checkedAt: number}|null} readCache Read cached check result (sync) + * @property {(data: {latest: string, checkedAt: number}) => void} writeCache Write check result to cache + * @property {string} currentVersion Current installed version + * @property {string} packageName Package name (for notification) + * @property {number} [cacheTtlMs] Cache TTL in ms (default 24h) */ -export function triggerUpdateCheck() { - _doCheck().catch(() => {}); -} -/** @internal */ -async function _doCheck() { - // Skip if cache is fresh - try { - const data = JSON.parse(readFileSync(CACHE_FILE, 'utf-8')); - if (Date.now() - data.checkedAt < CACHE_TTL_MS) return; - } catch { - // No cache or corrupt — proceed with fetch +/** + * Create an update checker service. + * @param {UpdateCheckPorts} ports + * @returns {{ getNotification: () => string|null, triggerCheck: (signal?: AbortSignal) => void }} + */ +export function createUpdateChecker(ports) { + const { fetchLatest, readCache, writeCache, currentVersion, packageName, + cacheTtlMs = DEFAULT_CACHE_TTL_MS } = ports; + + function getNotification() { + try { + const data = readCache(); + if (!data?.latest || !isNewer(currentVersion, data.latest)) return null; + return formatNotification(data.latest, currentVersion, packageName); + } catch { + return null; + } } - const controller = new AbortController(); - const timer = setTimeout(() => controller.abort(), FETCH_TIMEOUT_MS); - - try { - const res = await fetch(REGISTRY_URL, { - signal: controller.signal, - headers: { Accept: 'application/json' }, - }); - if (!res.ok) return; - const body = await res.json(); - const latest = body.version; - if (!latest) return; + function triggerCheck(signal) { + _doCheck(signal).catch(() => {}); + } + + async function _doCheck(signal) { + // Skip if cache is fresh + try { + const data = readCache(); + if (data && Date.now() - data.checkedAt < cacheTtlMs) return; + } catch { + // No cache or corrupt — proceed with fetch + } - mkdirSync(CACHE_DIR, { recursive: true }); - writeFileSync(CACHE_FILE, JSON.stringify({ latest, checkedAt: Date.now() })); - } finally { - clearTimeout(timer); + const latest = await fetchLatest(signal); + if (!latest) return; + writeCache({ latest, checkedAt: Date.now() }); } + + return { getNotification, triggerCheck }; +} + +// ── Default adapters (real I/O) ───────────────────────────────── + +/** + * Build real adapters for production use. + * Alfred is lazy-loaded only when a fetch is needed. + * @param {string} registryUrl npm registry URL for the package + * @param {object} [opts] + * @param {number} [opts.timeoutMs] Fetch timeout (default 3000) + * @returns {Pick} + */ +export function defaultPorts(registryUrl, opts = {}) { + const { timeoutMs = DEFAULT_FETCH_TIMEOUT_MS } = opts; + + /** @type {import('@git-stunts/alfred').Policy|null} */ + let policy = null; + + return { + readCache() { + try { + return JSON.parse(readFileSync(CACHE_FILE, 'utf-8')); + } catch { + return null; + } + }, + + writeCache(data) { + mkdirSync(CACHE_DIR, { recursive: true }); + writeFileSync(CACHE_FILE, JSON.stringify(data)); + }, + + async fetchLatest(signal) { + // Lazy-load Alfred on first fetch + const { Policy } = await import('@git-stunts/alfred'); + + // External signal goes on retry options for cancellation; + // timeout's internal signal is passed to fn for fetch abort. + const registryPolicy = Policy.timeout(timeoutMs) + .wrap(Policy.retry({ + retries: 1, + delay: 200, + backoff: 'exponential', + jitter: 'decorrelated', + signal, + })); + + const body = await registryPolicy.execute( + (timeoutSignal) => + fetch(registryUrl, { + signal: timeoutSignal, + headers: { Accept: 'application/json' }, + }).then((res) => { + if (!res.ok) throw new Error(`registry ${res.status}`); + return res.json(); + }), + ); + + return body.version || null; + }, + }; } diff --git a/test/update-check.test.js b/test/update-check.test.js index 61a3134a..c9b37b4c 100644 --- a/test/update-check.test.js +++ b/test/update-check.test.js @@ -1,14 +1,29 @@ /** * @module test/update-check - * Tests for update-check: semver comparison, cache, notification formatting. + * Tests for update-check: semver comparison, cache, notification, DI service. + * + * All tests use injected fakes — no network, no filesystem. */ -import { describe, it, expect, beforeEach, afterEach } from 'vitest'; -import { mkdtemp, rm, readFile, writeFile, mkdir } from 'node:fs/promises'; -import { join } from 'node:path'; -import { tmpdir } from 'node:os'; -import { isNewer, formatNotification, getUpdateNotification } from '../src/update-check.js'; -import { VERSION, NAME } from '../src/version.js'; +import { describe, it, expect } from 'vitest'; +import { isNewer, formatNotification, createUpdateChecker } from '../src/update-check.js'; + +// ── Helpers ───────────────────────────────────────────────────── + +/** Build a fake port set for createUpdateChecker. */ +function fakePorts(overrides = {}) { + return { + currentVersion: '1.0.0', + packageName: '@test/pkg', + cacheTtlMs: 1000, + readCache: () => null, + writeCache: () => {}, + fetchLatest: async () => null, + ...overrides, + }; +} + +// ── isNewer ───────────────────────────────────────────────────── describe('isNewer()', () => { it('detects newer major version', () => { @@ -40,29 +55,159 @@ describe('isNewer()', () => { }); }); +// ── formatNotification ────────────────────────────────────────── + describe('formatNotification()', () => { - it('includes current version', () => { - const note = formatNotification('99.0.0'); - expect(note).toContain(VERSION); + it('includes current and latest versions', () => { + const note = formatNotification('2.0.0', '1.0.0', '@test/pkg'); + expect(note).toContain('1.0.0'); + expect(note).toContain('2.0.0'); }); - it('includes latest version', () => { - const note = formatNotification('99.0.0'); - expect(note).toContain('99.0.0'); + it('includes install command with package name', () => { + const note = formatNotification('2.0.0', '1.0.0', '@test/pkg'); + expect(note).toContain('npm install -g @test/pkg'); }); +}); + +// ── createUpdateChecker ───────────────────────────────────────── + +describe('createUpdateChecker()', () => { + describe('getNotification()', () => { + it('returns null when cache is empty', () => { + const checker = createUpdateChecker(fakePorts()); + expect(checker.getNotification()).toBeNull(); + }); + + it('returns null when cache has same version', () => { + const checker = createUpdateChecker(fakePorts({ + readCache: () => ({ latest: '1.0.0', checkedAt: Date.now() }), + })); + expect(checker.getNotification()).toBeNull(); + }); - it('includes install command', () => { - const note = formatNotification('99.0.0'); - expect(note).toContain(`npm install -g ${NAME}`); + it('returns notification when cache has newer version', () => { + const checker = createUpdateChecker(fakePorts({ + readCache: () => ({ latest: '2.0.0', checkedAt: Date.now() }), + })); + const note = checker.getNotification(); + expect(note).toContain('2.0.0'); + expect(note).toContain('npm install -g @test/pkg'); + }); + + it('returns null when cache has older version', () => { + const checker = createUpdateChecker(fakePorts({ + readCache: () => ({ latest: '0.5.0', checkedAt: Date.now() }), + })); + expect(checker.getNotification()).toBeNull(); + }); + + it('returns null when readCache throws', () => { + const checker = createUpdateChecker(fakePorts({ + readCache: () => { throw new Error('corrupt'); }, + })); + expect(checker.getNotification()).toBeNull(); + }); }); -}); -describe('getUpdateNotification()', () => { - it('returns null when no cache exists', () => { - // getUpdateNotification reads from ~/.gitmind/update-check.json - // With no cache, it should return null - const result = getUpdateNotification(); - // Result depends on whether cache exists; either null or a string - expect(result === null || typeof result === 'string').toBe(true); + describe('triggerCheck()', () => { + it('fetches and writes cache when no cache exists', async () => { + let written = null; + const checker = createUpdateChecker(fakePorts({ + fetchLatest: async () => '2.0.0', + writeCache: (data) => { written = data; }, + })); + + checker.triggerCheck(); + // Wait for the fire-and-forget to settle + await new Promise((r) => setTimeout(r, 50)); + + expect(written).not.toBeNull(); + expect(written.latest).toBe('2.0.0'); + expect(written.checkedAt).toBeGreaterThan(0); + }); + + it('skips fetch when cache is fresh', async () => { + let fetched = false; + const checker = createUpdateChecker(fakePorts({ + readCache: () => ({ latest: '1.0.0', checkedAt: Date.now() }), + fetchLatest: async () => { fetched = true; return '2.0.0'; }, + })); + + checker.triggerCheck(); + await new Promise((r) => setTimeout(r, 50)); + + expect(fetched).toBe(false); + }); + + it('fetches when cache is stale', async () => { + let fetched = false; + const checker = createUpdateChecker(fakePorts({ + cacheTtlMs: 1000, + readCache: () => ({ latest: '1.0.0', checkedAt: Date.now() - 5000 }), + fetchLatest: async () => { fetched = true; return '2.0.0'; }, + })); + + checker.triggerCheck(); + await new Promise((r) => setTimeout(r, 50)); + + expect(fetched).toBe(true); + }); + + it('does not write cache when fetchLatest returns null', async () => { + let written = false; + const checker = createUpdateChecker(fakePorts({ + fetchLatest: async () => null, + writeCache: () => { written = true; }, + })); + + checker.triggerCheck(); + await new Promise((r) => setTimeout(r, 50)); + + expect(written).toBe(false); + }); + + it('silently swallows fetch errors', async () => { + const checker = createUpdateChecker(fakePorts({ + fetchLatest: async () => { throw new Error('network down'); }, + })); + + // Should not throw + checker.triggerCheck(); + await new Promise((r) => setTimeout(r, 50)); + }); + + it('passes signal to fetchLatest', async () => { + let receivedSignal = null; + const checker = createUpdateChecker(fakePorts({ + fetchLatest: async (signal) => { receivedSignal = signal; return '2.0.0'; }, + })); + + const controller = new AbortController(); + checker.triggerCheck(controller.signal); + await new Promise((r) => setTimeout(r, 50)); + + expect(receivedSignal).toBe(controller.signal); + }); + + it('respects abort signal', async () => { + let fetched = false; + const checker = createUpdateChecker(fakePorts({ + fetchLatest: async (signal) => { + // Simulate slow fetch + await new Promise((r) => setTimeout(r, 200)); + if (signal?.aborted) throw new Error('aborted'); + fetched = true; + return '2.0.0'; + }, + })); + + const controller = new AbortController(); + checker.triggerCheck(controller.signal); + controller.abort(); + await new Promise((r) => setTimeout(r, 300)); + + expect(fetched).toBe(false); + }); }); }); From 59e9bd55264cbc5ec73dafad73aaaa7ebeeb9f7d Mon Sep 17 00:00:00 2001 From: James Ross Date: Mon, 23 Feb 2026 05:46:53 -0800 Subject: [PATCH 6/8] test: Docker-isolated integration test runner (#287) CLI integration tests (contracts, content, version) now run in a Docker container with GIT_MIND_DISABLE_UPGRADE_CHECK=1 set at the image level. npm test runs unit tests only; npm run test:integration runs Docker. --- test/Dockerfile | 27 +++++++++++++++++++++++++++ 1 file changed, 27 insertions(+) create mode 100644 test/Dockerfile diff --git a/test/Dockerfile b/test/Dockerfile new file mode 100644 index 00000000..0dc31707 --- /dev/null +++ b/test/Dockerfile @@ -0,0 +1,27 @@ +FROM node:22-slim + +RUN apt-get update && apt-get install -y git python3 make g++ && rm -rf /var/lib/apt/lists/* + +# Disable upgrade check — no network egress in test container +ENV GIT_MIND_DISABLE_UPGRADE_CHECK=1 +ENV NO_COLOR=1 + +WORKDIR /app + +# Install deps first (layer cache) +COPY package.json package-lock.json ./ +RUN npm ci + +# Copy source +COPY bin/ bin/ +COPY src/ src/ +COPY test/ test/ +COPY docs/contracts/ docs/contracts/ +COPY extensions/ extensions/ + +# Git identity for tests that init repos +RUN git config --global user.email "test@test.com" && \ + git config --global user.name "Test" + +# Run only integration tests (files that spawn bin/git-mind.js) +CMD ["npx", "vitest", "run", "test/contracts.integration.test.js", "test/content.test.js", "test/version.test.js"] From d30d7d9fab1012eb921089d6ed8147ad2189a666 Mon Sep 17 00:00:00 2001 From: James Ross Date: Mon, 23 Feb 2026 12:19:52 -0800 Subject: [PATCH 7/8] chore: rename @neuroglyph/git-mind to @flyingrobots/git-mind (#289) Repository transferred to flyingrobots org. Updated npm package scope, GitHub URLs, schema $id fields, docs, and git remote. --- .reuse/dep5 | 2 +- CHANGELOG.md | 14 +++++++------- CONTRIBUTING.md | 2 +- GUIDE.md | 12 ++++++------ docs/contracts/cli/at.schema.json | 2 +- docs/contracts/cli/content-delete.schema.json | 2 +- docs/contracts/cli/content-meta.schema.json | 2 +- docs/contracts/cli/content-set.schema.json | 2 +- docs/contracts/cli/content-show.schema.json | 2 +- docs/contracts/cli/diff.schema.json | 2 +- docs/contracts/cli/doctor.schema.json | 2 +- docs/contracts/cli/export-data.schema.json | 2 +- docs/contracts/cli/export-file.schema.json | 2 +- docs/contracts/cli/extension-add.schema.json | 2 +- docs/contracts/cli/extension-list.schema.json | 2 +- docs/contracts/cli/extension-remove.schema.json | 2 +- docs/contracts/cli/extension-validate.schema.json | 2 +- docs/contracts/cli/import.schema.json | 2 +- docs/contracts/cli/merge.schema.json | 2 +- docs/contracts/cli/node-detail.schema.json | 2 +- docs/contracts/cli/node-list.schema.json | 2 +- docs/contracts/cli/review-batch.schema.json | 2 +- docs/contracts/cli/review-list.schema.json | 2 +- docs/contracts/cli/set.schema.json | 2 +- docs/contracts/cli/status.schema.json | 2 +- docs/contracts/cli/suggest.schema.json | 2 +- docs/contracts/cli/unset.schema.json | 2 +- docs/contracts/cli/view-lens.schema.json | 2 +- docs/contracts/cli/view-progress.schema.json | 2 +- install.sh | 4 ++-- package-lock.json | 4 ++-- package.json | 8 ++++---- src/format-pr.js | 2 +- src/index.js | 2 +- test/version.test.js | 2 +- uninstall.sh | 4 ++-- 36 files changed, 53 insertions(+), 53 deletions(-) diff --git a/.reuse/dep5 b/.reuse/dep5 index 57a11df4..dbbe9bf1 100644 --- a/.reuse/dep5 +++ b/.reuse/dep5 @@ -1,7 +1,7 @@ Format: https://www.debian.org/doc/packaging-manuals/copyright-format/1.0/ Upstream-Name: git-mind Upstream-Contact: James Ross -Source: https://github.com/neuroglyph/git-mind +Source: https://github.com/flyingrobots/git-mind Files: * Copyright: 2025-2026 James Ross diff --git a/CHANGELOG.md b/CHANGELOG.md index af4c5ebb..c329255b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -357,10 +357,10 @@ Complete rewrite from C23 to Node.js on `@git-stunts/git-warp`. - Docker-based CI/CD - All C-specific documentation -[3.1.0]: https://github.com/neuroglyph/git-mind/releases/tag/v3.1.0 -[3.0.0]: https://github.com/neuroglyph/git-mind/releases/tag/v3.0.0 -[2.0.0-alpha.5]: https://github.com/neuroglyph/git-mind/releases/tag/v2.0.0-alpha.5 -[2.0.0-alpha.4]: https://github.com/neuroglyph/git-mind/releases/tag/v2.0.0-alpha.4 -[2.0.0-alpha.3]: https://github.com/neuroglyph/git-mind/releases/tag/v2.0.0-alpha.3 -[2.0.0-alpha.2]: https://github.com/neuroglyph/git-mind/releases/tag/v2.0.0-alpha.2 -[2.0.0-alpha.0]: https://github.com/neuroglyph/git-mind/releases/tag/v2.0.0-alpha.0 +[3.1.0]: https://github.com/flyingrobots/git-mind/releases/tag/v3.1.0 +[3.0.0]: https://github.com/flyingrobots/git-mind/releases/tag/v3.0.0 +[2.0.0-alpha.5]: https://github.com/flyingrobots/git-mind/releases/tag/v2.0.0-alpha.5 +[2.0.0-alpha.4]: https://github.com/flyingrobots/git-mind/releases/tag/v2.0.0-alpha.4 +[2.0.0-alpha.3]: https://github.com/flyingrobots/git-mind/releases/tag/v2.0.0-alpha.3 +[2.0.0-alpha.2]: https://github.com/flyingrobots/git-mind/releases/tag/v2.0.0-alpha.2 +[2.0.0-alpha.0]: https://github.com/flyingrobots/git-mind/releases/tag/v2.0.0-alpha.0 diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 0b14ab65..195b1ca5 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -11,7 +11,7 @@ Thanks for your interest in contributing. This document covers the essentials. ## Setup ```bash -git clone https://github.com/neuroglyph/git-mind.git +git clone https://github.com/flyingrobots/git-mind.git cd git-mind npm install npm test diff --git a/GUIDE.md b/GUIDE.md index 2f554db3..10215455 100644 --- a/GUIDE.md +++ b/GUIDE.md @@ -51,7 +51,7 @@ git-mind captures those relationships explicitly, so you can query them, visuali ### From source ```bash -git clone https://github.com/neuroglyph/git-mind.git +git clone https://github.com/flyingrobots/git-mind.git cd git-mind npm install ``` @@ -484,7 +484,7 @@ Status values are normalized on read — `Done`, `DONE`, `complete`, `finished` ### Custom views (programmatic) ```javascript -import { defineView, renderView, loadGraph } from '@neuroglyph/git-mind'; +import { defineView, renderView, loadGraph } from '@flyingrobots/git-mind'; defineView('my-view', (nodes, edges) => ({ nodes: nodes.filter(n => n.startsWith('feature:')), @@ -544,7 +544,7 @@ git mind import graph.yaml --dry-run ### Programmatic import ```javascript -import { importFile, loadGraph } from '@neuroglyph/git-mind'; +import { importFile, loadGraph } from '@flyingrobots/git-mind'; const graph = await loadGraph('.'); const result = await importFile(graph, 'graph.yaml', { dryRun: false }); @@ -586,7 +586,7 @@ Edges created from directives get a confidence of **0.8** — high, but flagged ### Processing commits programmatically ```javascript -import { processCommit, loadGraph } from '@neuroglyph/git-mind'; +import { processCommit, loadGraph } from '@flyingrobots/git-mind'; const graph = await loadGraph('.'); await processCommit(graph, { @@ -641,7 +641,7 @@ When you run `git mind at `: ### Programmatic usage ```javascript -import { loadGraph, getEpochForRef, computeStatus, getCurrentTick, recordEpoch } from '@neuroglyph/git-mind'; +import { loadGraph, getEpochForRef, computeStatus, getCurrentTick, recordEpoch } from '@flyingrobots/git-mind'; const graph = await loadGraph('.'); @@ -745,7 +745,7 @@ import { defineView, renderView, listViews, classifyStatus, // Hooks parseDirectives, processCommit, -} from '@neuroglyph/git-mind'; +} from '@flyingrobots/git-mind'; ``` ### Initialize and load diff --git a/docs/contracts/cli/at.schema.json b/docs/contracts/cli/at.schema.json index ae5f982e..eee9750b 100644 --- a/docs/contracts/cli/at.schema.json +++ b/docs/contracts/cli/at.schema.json @@ -1,6 +1,6 @@ { "$schema": "https://json-schema.org/draft/2020-12/schema", - "$id": "https://github.com/neuroglyph/git-mind/docs/contracts/cli/at.schema.json", + "$id": "https://github.com/flyingrobots/git-mind/docs/contracts/cli/at.schema.json", "title": "git-mind at --json", "description": "Time-travel output from `git mind at --json`", "type": "object", diff --git a/docs/contracts/cli/content-delete.schema.json b/docs/contracts/cli/content-delete.schema.json index 604137ab..427fce3f 100644 --- a/docs/contracts/cli/content-delete.schema.json +++ b/docs/contracts/cli/content-delete.schema.json @@ -1,6 +1,6 @@ { "$schema": "https://json-schema.org/draft/2020-12/schema", - "$id": "https://github.com/neuroglyph/git-mind/docs/contracts/cli/content-delete.schema.json", + "$id": "https://github.com/flyingrobots/git-mind/docs/contracts/cli/content-delete.schema.json", "title": "git-mind content delete --json", "description": "Content deletion result from `git mind content delete --json`", "type": "object", diff --git a/docs/contracts/cli/content-meta.schema.json b/docs/contracts/cli/content-meta.schema.json index 18329633..c8cc9844 100644 --- a/docs/contracts/cli/content-meta.schema.json +++ b/docs/contracts/cli/content-meta.schema.json @@ -1,6 +1,6 @@ { "$schema": "https://json-schema.org/draft/2020-12/schema", - "$id": "https://github.com/neuroglyph/git-mind/docs/contracts/cli/content-meta.schema.json", + "$id": "https://github.com/flyingrobots/git-mind/docs/contracts/cli/content-meta.schema.json", "title": "git-mind content meta --json", "description": "Content metadata result from `git mind content meta --json`", "type": "object", diff --git a/docs/contracts/cli/content-set.schema.json b/docs/contracts/cli/content-set.schema.json index 25f9e672..991aa8fb 100644 --- a/docs/contracts/cli/content-set.schema.json +++ b/docs/contracts/cli/content-set.schema.json @@ -1,6 +1,6 @@ { "$schema": "https://json-schema.org/draft/2020-12/schema", - "$id": "https://github.com/neuroglyph/git-mind/docs/contracts/cli/content-set.schema.json", + "$id": "https://github.com/flyingrobots/git-mind/docs/contracts/cli/content-set.schema.json", "title": "git-mind content set --json", "description": "Content attachment result from `git mind content set --json`", "type": "object", diff --git a/docs/contracts/cli/content-show.schema.json b/docs/contracts/cli/content-show.schema.json index 949fee4c..e58156ce 100644 --- a/docs/contracts/cli/content-show.schema.json +++ b/docs/contracts/cli/content-show.schema.json @@ -1,6 +1,6 @@ { "$schema": "https://json-schema.org/draft/2020-12/schema", - "$id": "https://github.com/neuroglyph/git-mind/docs/contracts/cli/content-show.schema.json", + "$id": "https://github.com/flyingrobots/git-mind/docs/contracts/cli/content-show.schema.json", "title": "git-mind content show --json", "description": "Content display result from `git mind content show --json`", "type": "object", diff --git a/docs/contracts/cli/diff.schema.json b/docs/contracts/cli/diff.schema.json index cbf87fd8..20500782 100644 --- a/docs/contracts/cli/diff.schema.json +++ b/docs/contracts/cli/diff.schema.json @@ -1,6 +1,6 @@ { "$schema": "https://json-schema.org/draft/2020-12/schema", - "$id": "https://github.com/neuroglyph/git-mind/docs/contracts/cli/diff.schema.json", + "$id": "https://github.com/flyingrobots/git-mind/docs/contracts/cli/diff.schema.json", "title": "git-mind diff --json", "description": "Graph diff result from `git mind diff .. --json`", "type": "object", diff --git a/docs/contracts/cli/doctor.schema.json b/docs/contracts/cli/doctor.schema.json index 6ac7ce5f..031e1da2 100644 --- a/docs/contracts/cli/doctor.schema.json +++ b/docs/contracts/cli/doctor.schema.json @@ -1,6 +1,6 @@ { "$schema": "https://json-schema.org/draft/2020-12/schema", - "$id": "https://github.com/neuroglyph/git-mind/docs/contracts/cli/doctor.schema.json", + "$id": "https://github.com/flyingrobots/git-mind/docs/contracts/cli/doctor.schema.json", "title": "git-mind doctor --json", "description": "Graph integrity check result from `git mind doctor --json`", "type": "object", diff --git a/docs/contracts/cli/export-data.schema.json b/docs/contracts/cli/export-data.schema.json index 2756edcb..b856735b 100644 --- a/docs/contracts/cli/export-data.schema.json +++ b/docs/contracts/cli/export-data.schema.json @@ -1,6 +1,6 @@ { "$schema": "https://json-schema.org/draft/2020-12/schema", - "$id": "https://github.com/neuroglyph/git-mind/docs/contracts/cli/export-data.schema.json", + "$id": "https://github.com/flyingrobots/git-mind/docs/contracts/cli/export-data.schema.json", "title": "git-mind export --json (stdout)", "description": "Graph data export from `git mind export --json` (stdout mode)", "type": "object", diff --git a/docs/contracts/cli/export-file.schema.json b/docs/contracts/cli/export-file.schema.json index f066077d..706d4833 100644 --- a/docs/contracts/cli/export-file.schema.json +++ b/docs/contracts/cli/export-file.schema.json @@ -1,6 +1,6 @@ { "$schema": "https://json-schema.org/draft/2020-12/schema", - "$id": "https://github.com/neuroglyph/git-mind/docs/contracts/cli/export-file.schema.json", + "$id": "https://github.com/flyingrobots/git-mind/docs/contracts/cli/export-file.schema.json", "title": "git-mind export --file --json", "description": "File export result from `git mind export --file --json`", "type": "object", diff --git a/docs/contracts/cli/extension-add.schema.json b/docs/contracts/cli/extension-add.schema.json index b8192095..28b359aa 100644 --- a/docs/contracts/cli/extension-add.schema.json +++ b/docs/contracts/cli/extension-add.schema.json @@ -1,6 +1,6 @@ { "$schema": "https://json-schema.org/draft/2020-12/schema", - "$id": "https://github.com/neuroglyph/git-mind/docs/contracts/cli/extension-add.schema.json", + "$id": "https://github.com/flyingrobots/git-mind/docs/contracts/cli/extension-add.schema.json", "title": "git-mind extension add --json", "description": "Extension registration result from `git mind extension add --json`", "type": "object", diff --git a/docs/contracts/cli/extension-list.schema.json b/docs/contracts/cli/extension-list.schema.json index b96721b9..4402714a 100644 --- a/docs/contracts/cli/extension-list.schema.json +++ b/docs/contracts/cli/extension-list.schema.json @@ -1,6 +1,6 @@ { "$schema": "https://json-schema.org/draft/2020-12/schema", - "$id": "https://github.com/neuroglyph/git-mind/docs/contracts/cli/extension-list.schema.json", + "$id": "https://github.com/flyingrobots/git-mind/docs/contracts/cli/extension-list.schema.json", "title": "git-mind extension list --json", "description": "Registered extensions from `git mind extension list --json`", "type": "object", diff --git a/docs/contracts/cli/extension-remove.schema.json b/docs/contracts/cli/extension-remove.schema.json index 65b124a4..5277e424 100644 --- a/docs/contracts/cli/extension-remove.schema.json +++ b/docs/contracts/cli/extension-remove.schema.json @@ -1,6 +1,6 @@ { "$schema": "https://json-schema.org/draft/2020-12/schema", - "$id": "https://github.com/neuroglyph/git-mind/docs/contracts/cli/extension-remove.schema.json", + "$id": "https://github.com/flyingrobots/git-mind/docs/contracts/cli/extension-remove.schema.json", "title": "git-mind extension remove --json", "description": "Extension removal result from `git mind extension remove --json`", "type": "object", diff --git a/docs/contracts/cli/extension-validate.schema.json b/docs/contracts/cli/extension-validate.schema.json index 21ce74fd..eccf43fa 100644 --- a/docs/contracts/cli/extension-validate.schema.json +++ b/docs/contracts/cli/extension-validate.schema.json @@ -1,6 +1,6 @@ { "$schema": "https://json-schema.org/draft/2020-12/schema", - "$id": "https://github.com/neuroglyph/git-mind/docs/contracts/cli/extension-validate.schema.json", + "$id": "https://github.com/flyingrobots/git-mind/docs/contracts/cli/extension-validate.schema.json", "title": "git-mind extension validate --json", "description": "Extension manifest validation result from `git mind extension validate --json`", "type": "object", diff --git a/docs/contracts/cli/import.schema.json b/docs/contracts/cli/import.schema.json index f97d2fce..44151a83 100644 --- a/docs/contracts/cli/import.schema.json +++ b/docs/contracts/cli/import.schema.json @@ -1,6 +1,6 @@ { "$schema": "https://json-schema.org/draft/2020-12/schema", - "$id": "https://github.com/neuroglyph/git-mind/docs/contracts/cli/import.schema.json", + "$id": "https://github.com/flyingrobots/git-mind/docs/contracts/cli/import.schema.json", "title": "git-mind import --json", "description": "Import result from `git mind import --json` or `git mind import --from-markdown --json`", "type": "object", diff --git a/docs/contracts/cli/merge.schema.json b/docs/contracts/cli/merge.schema.json index 7fa8c4e0..72751414 100644 --- a/docs/contracts/cli/merge.schema.json +++ b/docs/contracts/cli/merge.schema.json @@ -1,6 +1,6 @@ { "$schema": "https://json-schema.org/draft/2020-12/schema", - "$id": "https://github.com/neuroglyph/git-mind/docs/contracts/cli/merge.schema.json", + "$id": "https://github.com/flyingrobots/git-mind/docs/contracts/cli/merge.schema.json", "title": "git-mind merge --json", "description": "Merge result from `git mind merge --json`", "type": "object", diff --git a/docs/contracts/cli/node-detail.schema.json b/docs/contracts/cli/node-detail.schema.json index a82eb050..86948b21 100644 --- a/docs/contracts/cli/node-detail.schema.json +++ b/docs/contracts/cli/node-detail.schema.json @@ -1,6 +1,6 @@ { "$schema": "https://json-schema.org/draft/2020-12/schema", - "$id": "https://github.com/neuroglyph/git-mind/docs/contracts/cli/node-detail.schema.json", + "$id": "https://github.com/flyingrobots/git-mind/docs/contracts/cli/node-detail.schema.json", "title": "git-mind nodes --id --json", "description": "Single node detail output from `git mind nodes --id --json`", "type": "object", diff --git a/docs/contracts/cli/node-list.schema.json b/docs/contracts/cli/node-list.schema.json index b59c3142..91f4c690 100644 --- a/docs/contracts/cli/node-list.schema.json +++ b/docs/contracts/cli/node-list.schema.json @@ -1,6 +1,6 @@ { "$schema": "https://json-schema.org/draft/2020-12/schema", - "$id": "https://github.com/neuroglyph/git-mind/docs/contracts/cli/node-list.schema.json", + "$id": "https://github.com/flyingrobots/git-mind/docs/contracts/cli/node-list.schema.json", "title": "git-mind nodes --json", "description": "Node list output from `git mind nodes --json`", "type": "object", diff --git a/docs/contracts/cli/review-batch.schema.json b/docs/contracts/cli/review-batch.schema.json index 3a4f677a..5932608a 100644 --- a/docs/contracts/cli/review-batch.schema.json +++ b/docs/contracts/cli/review-batch.schema.json @@ -1,6 +1,6 @@ { "$schema": "https://json-schema.org/draft/2020-12/schema", - "$id": "https://github.com/neuroglyph/git-mind/docs/contracts/cli/review-batch.schema.json", + "$id": "https://github.com/flyingrobots/git-mind/docs/contracts/cli/review-batch.schema.json", "title": "git-mind review --batch --json", "description": "Batch review result from `git mind review --batch --json`", "type": "object", diff --git a/docs/contracts/cli/review-list.schema.json b/docs/contracts/cli/review-list.schema.json index 345f20e0..859d4c9d 100644 --- a/docs/contracts/cli/review-list.schema.json +++ b/docs/contracts/cli/review-list.schema.json @@ -1,6 +1,6 @@ { "$schema": "https://json-schema.org/draft/2020-12/schema", - "$id": "https://github.com/neuroglyph/git-mind/docs/contracts/cli/review-list.schema.json", + "$id": "https://github.com/flyingrobots/git-mind/docs/contracts/cli/review-list.schema.json", "title": "git-mind review --json", "description": "Pending review list from `git mind review --json`", "type": "object", diff --git a/docs/contracts/cli/set.schema.json b/docs/contracts/cli/set.schema.json index 14234b23..e3326fb2 100644 --- a/docs/contracts/cli/set.schema.json +++ b/docs/contracts/cli/set.schema.json @@ -1,6 +1,6 @@ { "$schema": "https://json-schema.org/draft/2020-12/schema", - "$id": "https://github.com/neuroglyph/git-mind/docs/contracts/cli/set.schema.json", + "$id": "https://github.com/flyingrobots/git-mind/docs/contracts/cli/set.schema.json", "title": "git-mind set --json", "description": "Output from `git mind set --json`", "type": "object", diff --git a/docs/contracts/cli/status.schema.json b/docs/contracts/cli/status.schema.json index d524b136..30bdba19 100644 --- a/docs/contracts/cli/status.schema.json +++ b/docs/contracts/cli/status.schema.json @@ -1,6 +1,6 @@ { "$schema": "https://json-schema.org/draft/2020-12/schema", - "$id": "https://github.com/neuroglyph/git-mind/docs/contracts/cli/status.schema.json", + "$id": "https://github.com/flyingrobots/git-mind/docs/contracts/cli/status.schema.json", "title": "git-mind status --json", "description": "Graph status dashboard output from `git mind status --json`", "type": "object", diff --git a/docs/contracts/cli/suggest.schema.json b/docs/contracts/cli/suggest.schema.json index bed3165c..9e43a1e6 100644 --- a/docs/contracts/cli/suggest.schema.json +++ b/docs/contracts/cli/suggest.schema.json @@ -1,6 +1,6 @@ { "$schema": "https://json-schema.org/draft/2020-12/schema", - "$id": "https://github.com/neuroglyph/git-mind/docs/contracts/cli/suggest.schema.json", + "$id": "https://github.com/flyingrobots/git-mind/docs/contracts/cli/suggest.schema.json", "title": "git-mind suggest --json", "description": "AI-powered edge suggestion result from `git mind suggest --json`", "type": "object", diff --git a/docs/contracts/cli/unset.schema.json b/docs/contracts/cli/unset.schema.json index ea1ce99a..03d52915 100644 --- a/docs/contracts/cli/unset.schema.json +++ b/docs/contracts/cli/unset.schema.json @@ -1,6 +1,6 @@ { "$schema": "https://json-schema.org/draft/2020-12/schema", - "$id": "https://github.com/neuroglyph/git-mind/docs/contracts/cli/unset.schema.json", + "$id": "https://github.com/flyingrobots/git-mind/docs/contracts/cli/unset.schema.json", "title": "git-mind unset --json", "description": "Output from `git mind unset --json`", "type": "object", diff --git a/docs/contracts/cli/view-lens.schema.json b/docs/contracts/cli/view-lens.schema.json index bf838391..383d978b 100644 --- a/docs/contracts/cli/view-lens.schema.json +++ b/docs/contracts/cli/view-lens.schema.json @@ -1,6 +1,6 @@ { "$schema": "https://json-schema.org/draft/2020-12/schema", - "$id": "https://github.com/neuroglyph/git-mind/docs/contracts/cli/view-lens.schema.json", + "$id": "https://github.com/flyingrobots/git-mind/docs/contracts/cli/view-lens.schema.json", "title": "git-mind view with lenses --json", "description": "View output with lens chaining from `git mind view :: --json`", "type": "object", diff --git a/docs/contracts/cli/view-progress.schema.json b/docs/contracts/cli/view-progress.schema.json index dcab49bd..81145d20 100644 --- a/docs/contracts/cli/view-progress.schema.json +++ b/docs/contracts/cli/view-progress.schema.json @@ -1,6 +1,6 @@ { "$schema": "https://json-schema.org/draft/2020-12/schema", - "$id": "https://github.com/neuroglyph/git-mind/docs/contracts/cli/view-progress.schema.json", + "$id": "https://github.com/flyingrobots/git-mind/docs/contracts/cli/view-progress.schema.json", "title": "git-mind view progress --json", "description": "Progress view output from `git mind view progress --json`", "type": "object", diff --git a/install.sh b/install.sh index 96f60a7c..3a34b14b 100755 --- a/install.sh +++ b/install.sh @@ -1,12 +1,12 @@ #!/bin/sh -# install.sh — Install @neuroglyph/git-mind globally +# install.sh — Install @flyingrobots/git-mind globally # Usage: # ./install.sh npm install -g # ./install.sh --prefix ~/.local install to ~/.local/bin # curl -fsSL | sh pipe-friendly set -e -PACKAGE="@neuroglyph/git-mind" +PACKAGE="@flyingrobots/git-mind" MIN_NODE=22 PREFIX="" diff --git a/package-lock.json b/package-lock.json index 8c43acc2..72249040 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,11 +1,11 @@ { - "name": "@neuroglyph/git-mind", + "name": "@flyingrobots/git-mind", "version": "4.0.1", "lockfileVersion": 3, "requires": true, "packages": { "": { - "name": "@neuroglyph/git-mind", + "name": "@flyingrobots/git-mind", "version": "4.0.1", "license": "Apache-2.0", "dependencies": { diff --git a/package.json b/package.json index dcf8c881..135f591e 100644 --- a/package.json +++ b/package.json @@ -1,5 +1,5 @@ { - "name": "@neuroglyph/git-mind", + "name": "@flyingrobots/git-mind", "version": "4.0.1", "description": "A project knowledge graph tool built on git-warp", "type": "module", @@ -7,11 +7,11 @@ "author": "James Ross ", "repository": { "type": "git", - "url": "https://github.com/neuroglyph/git-mind.git" + "url": "https://github.com/flyingrobots/git-mind.git" }, - "homepage": "https://github.com/neuroglyph/git-mind#readme", + "homepage": "https://github.com/flyingrobots/git-mind#readme", "bugs": { - "url": "https://github.com/neuroglyph/git-mind/issues" + "url": "https://github.com/flyingrobots/git-mind/issues" }, "bin": { "git-mind": "bin/git-mind.js" diff --git a/src/format-pr.js b/src/format-pr.js index 6bd9a91b..591be716 100644 --- a/src/format-pr.js +++ b/src/format-pr.js @@ -47,7 +47,7 @@ export function formatSuggestionsAsMarkdown(suggestions) { lines.push(''); lines.push(''); lines.push('---'); - lines.push('*Posted by [git-mind](https://github.com/neuroglyph/git-mind)*'); + lines.push('*Posted by [git-mind](https://github.com/flyingrobots/git-mind)*'); return lines.join('\n'); } diff --git a/src/index.js b/src/index.js index 08b58330..c3434166 100644 --- a/src/index.js +++ b/src/index.js @@ -1,5 +1,5 @@ /** - * @module @neuroglyph/git-mind + * @module @flyingrobots/git-mind * Public API for git-mind — a project knowledge graph tool built on git-warp. */ diff --git a/test/version.test.js b/test/version.test.js index 6343a23a..d6873fc5 100644 --- a/test/version.test.js +++ b/test/version.test.js @@ -16,7 +16,7 @@ describe('version module', () => { }); it('NAME is the scoped package name', () => { - expect(NAME).toBe('@neuroglyph/git-mind'); + expect(NAME).toBe('@flyingrobots/git-mind'); }); }); diff --git a/uninstall.sh b/uninstall.sh index ffa08a57..0387a07f 100755 --- a/uninstall.sh +++ b/uninstall.sh @@ -1,11 +1,11 @@ #!/bin/sh -# uninstall.sh — Remove @neuroglyph/git-mind +# uninstall.sh — Remove @flyingrobots/git-mind # Usage: # ./uninstall.sh npm uninstall -g # ./uninstall.sh --prefix ~/.local remove from ~/.local set -e -PACKAGE="@neuroglyph/git-mind" +PACKAGE="@flyingrobots/git-mind" PREFIX="" CLEAN_CACHE="" From 720b6c411f33c47a7013918f3a19fc90d90e3ff0 Mon Sep 17 00:00:00 2001 From: James Ross Date: Tue, 24 Feb 2026 15:07:12 -0800 Subject: [PATCH 8/8] fix: remove unused policy variable in update-check (#293) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Dead code — `registryPolicy` is created fresh in each `fetchLatest` call. --- src/update-check.js | 3 --- 1 file changed, 3 deletions(-) diff --git a/src/update-check.js b/src/update-check.js index 82a4433b..52cf1b54 100644 --- a/src/update-check.js +++ b/src/update-check.js @@ -117,9 +117,6 @@ export function createUpdateChecker(ports) { export function defaultPorts(registryUrl, opts = {}) { const { timeoutMs = DEFAULT_FETCH_TIMEOUT_MS } = opts; - /** @type {import('@git-stunts/alfred').Policy|null} */ - let policy = null; - return { readCache() { try {