Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
19 changes: 19 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,20 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0

## [Unreleased]

## [5.0.0] - 2026-02-25

### Breaking

- **`getNodes(graph)` wrapper** — Use `graph.getNodes()` directly (#295)
- **`hasNode(graph, id)` wrapper** — Use `graph.hasNode(id)` directly (#295)
- **`saveGraph(graph)` wrapper** — Dead code with zero call sites (#295)
- **`queryEdges(graph, filter)` wrapper** — Use `graph.getEdges()` with inline filter (#295)
- **`getNodesByPrefix(graph, prefix)` wrapper** — Use `graph.getNodes()` with `startsWith()` filter (#295)

### Changed

- **All internal `loadGraph()` calls replaced with `initGraph()`** — `loadGraph` kept as deprecated alias for public API backward compatibility (#295)

## [4.0.1] - 2026-02-22

### Fixed
Expand Down Expand Up @@ -357,6 +371,11 @@ Complete rewrite from C23 to Node.js on `@git-stunts/git-warp`.
- Docker-based CI/CD
- All C-specific documentation

[5.0.0]: https://github.com/neuroglyph/git-mind/releases/tag/v5.0.0
[4.0.1]: https://github.com/neuroglyph/git-mind/releases/tag/v4.0.1
[4.0.0]: https://github.com/neuroglyph/git-mind/releases/tag/v4.0.0
[3.3.0]: https://github.com/neuroglyph/git-mind/releases/tag/v3.3.0
[3.2.0]: https://github.com/neuroglyph/git-mind/releases/tag/v3.2.0
[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
Expand Down
4 changes: 2 additions & 2 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@neuroglyph/git-mind",
"version": "4.0.1",
"version": "5.0.0",
"description": "A project knowledge graph tool built on git-warp",
"type": "module",
"license": "Apache-2.0",
Expand Down
57 changes: 32 additions & 25 deletions src/cli/commands.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,9 +6,9 @@
import { execFileSync } from 'node:child_process';
import { writeFile, chmod, access, constants, readFile } from 'node:fs/promises';
import { join, extname } from 'node:path';
import { initGraph, loadGraph } from '../graph.js';
import { createEdge, queryEdges, removeEdge } from '../edges.js';
import { getNodes, getNode, getNodesByPrefix, setNodeProperty, unsetNodeProperty } from '../nodes.js';
import { initGraph } from '../graph.js';
import { createEdge, removeEdge } from '../edges.js';
import { getNode, setNodeProperty, unsetNodeProperty } from '../nodes.js';
import { computeStatus } from '../status.js';
import { importFile } from '../import.js';
import { importFromMarkdown } from '../frontmatter.js';
Expand Down Expand Up @@ -62,11 +62,11 @@ export async function resolveContext(cwd, envelope) {
let graph;

if (asOf === 'HEAD') {
graph = await loadGraph(cwd, { writerId: 'ctx-reader' });
graph = await initGraph(cwd, { writerId: 'ctx-reader' });
} else {
// Time-travel: resolve git ref → Lamport tick → materialize
// Use a separate resolver instance so we can materialize a fresh one.
const resolver = await loadGraph(cwd, { writerId: 'ctx-resolver' });
const resolver = await initGraph(cwd, { writerId: 'ctx-resolver' });
const result = await getEpochForRef(resolver, cwd, asOf);
if (!result) {
throw new Error(
Expand All @@ -76,7 +76,7 @@ export async function resolveContext(cwd, envelope) {
}
resolvedTick = result.epoch.tick;
// materialize({ ceiling }) is destructive — use a dedicated instance
graph = await loadGraph(cwd, { writerId: 'ctx-temporal' });
graph = await initGraph(cwd, { writerId: 'ctx-temporal' });
await graph.materialize({ ceiling: resolvedTick });
}

Expand Down Expand Up @@ -139,7 +139,7 @@ export async function link(cwd, source, target, opts = {}) {
const tgt = opts.remote ? qualifyNodeId(target, opts.remote) : target;

try {
const graph = await loadGraph(cwd);
const graph = await initGraph(cwd);
await createEdge(graph, { source: src, target: tgt, type, confidence: opts.confidence });
console.log(success(`${src} --[${type}]--> ${tgt}`));
} catch (err) {
Expand Down Expand Up @@ -208,8 +208,14 @@ export async function view(cwd, viewSpec, opts = {}) {
*/
export async function list(cwd, filter = {}) {
try {
const graph = await loadGraph(cwd);
const edges = await queryEdges(graph, filter);
const graph = await initGraph(cwd);
const allEdges = await graph.getEdges();
const edges = allEdges.filter(edge => {
if (filter.source && edge.from !== filter.source) return false;
if (filter.target && edge.to !== filter.target) return false;
if (filter.type && edge.label !== filter.type) return false;
return true;
});

if (edges.length === 0) {
console.log(info('No edges found'));
Expand Down Expand Up @@ -237,7 +243,7 @@ export async function remove(cwd, source, target, opts = {}) {
const type = opts.type ?? 'relates-to';

try {
const graph = await loadGraph(cwd);
const graph = await initGraph(cwd);
await removeEdge(graph, source, target, type);
console.log(success(`Removed: ${source} --[${type}]--> ${target}`));
} catch (err) {
Expand Down Expand Up @@ -299,7 +305,7 @@ export async function processCommitCmd(cwd, sha) {
try {
execFileSync('git', ['rev-parse', '--verify', sha], { cwd, encoding: 'utf-8' });
const message = execFileSync('git', ['log', '-1', '--format=%B', sha], { cwd, encoding: 'utf-8' });
const graph = await loadGraph(cwd);
const graph = await initGraph(cwd);
const directives = await processCommit(graph, { sha, message });

if (directives.length > 0) {
Expand Down Expand Up @@ -340,9 +346,10 @@ export async function nodes(cwd, opts = {}) {
}

// List nodes (optionally filtered by prefix)
const allNodes = await graph.getNodes();
const nodeList = opts.prefix
? await getNodesByPrefix(graph, opts.prefix)
: await getNodes(graph);
? allNodes.filter(n => n.startsWith(opts.prefix + ':'))
: allNodes;

if (opts.json) {
outputJson('nodes', { nodes: nodeList, resolvedContext });
Expand Down Expand Up @@ -398,7 +405,7 @@ export async function at(cwd, ref, opts = {}) {
}

try {
const graph = await loadGraph(cwd);
const graph = await initGraph(cwd);
const result = await getEpochForRef(graph, cwd, ref);

if (!result) {
Expand Down Expand Up @@ -441,7 +448,7 @@ export async function at(cwd, ref, opts = {}) {
*/
export async function importCmd(cwd, filePath, opts = {}) {
try {
const graph = await loadGraph(cwd);
const graph = await initGraph(cwd);
const result = await importFile(graph, filePath, { dryRun: opts.dryRun });

if (opts.json) {
Expand All @@ -467,7 +474,7 @@ export async function importCmd(cwd, filePath, opts = {}) {
*/
export async function importMarkdownCmd(cwd, pattern, opts = {}) {
try {
const graph = await loadGraph(cwd);
const graph = await initGraph(cwd);
const result = await importFromMarkdown(graph, cwd, pattern, { dryRun: opts.dryRun });

if (opts.json) {
Expand Down Expand Up @@ -534,7 +541,7 @@ export async function mergeCmd(cwd, opts = {}) {
}

try {
const graph = await loadGraph(cwd);
const graph = await initGraph(cwd);
const result = await mergeFromRepo(graph, opts.from, {
repoName: opts.repoName,
dryRun: opts.dryRun,
Expand Down Expand Up @@ -594,7 +601,7 @@ export async function doctor(cwd, opts = {}) {
*/
export async function suggest(cwd, opts = {}) {
try {
const graph = await loadGraph(cwd);
const graph = await initGraph(cwd);
const result = await generateSuggestions(cwd, graph, {
agent: opts.agent,
range: opts.context,
Expand All @@ -618,7 +625,7 @@ export async function suggest(cwd, opts = {}) {
*/
export async function review(cwd, opts = {}) {
try {
const graph = await loadGraph(cwd);
const graph = await initGraph(cwd);

// Batch mode
if (opts.batch) {
Expand Down Expand Up @@ -736,7 +743,7 @@ export async function set(cwd, nodeId, key, value, opts = {}) {
}

try {
const graph = await loadGraph(cwd);
const graph = await initGraph(cwd);
const result = await setNodeProperty(graph, nodeId, key, value);

if (opts.json) {
Expand Down Expand Up @@ -769,7 +776,7 @@ export async function unsetCmd(cwd, nodeId, key, opts = {}) {
}

try {
const graph = await loadGraph(cwd);
const graph = await initGraph(cwd);
const result = await unsetNodeProperty(graph, nodeId, key);

if (opts.json) {
Expand Down Expand Up @@ -838,7 +845,7 @@ export async function contentSet(cwd, nodeId, filePath, opts = {}) {
const buf = await readFile(filePath);
const mime = opts.mime ?? MIME_MAP[extname(filePath).toLowerCase()] ?? 'application/octet-stream';

const graph = await loadGraph(cwd);
const graph = await initGraph(cwd);
const result = await writeContent(graph, nodeId, buf, { mime });

if (opts.json) {
Expand All @@ -861,7 +868,7 @@ export async function contentSet(cwd, nodeId, filePath, opts = {}) {
*/
export async function contentShow(cwd, nodeId, opts = {}) {
try {
const graph = await loadGraph(cwd);
const graph = await initGraph(cwd);
const { content, meta } = await readContent(graph, nodeId);

if (opts.json) {
Expand Down Expand Up @@ -890,7 +897,7 @@ export async function contentShow(cwd, nodeId, opts = {}) {
*/
export async function contentMeta(cwd, nodeId, opts = {}) {
try {
const graph = await loadGraph(cwd);
const graph = await initGraph(cwd);
const meta = await getContentMeta(graph, nodeId);

if (!meta) {
Expand Down Expand Up @@ -921,7 +928,7 @@ export async function contentMeta(cwd, nodeId, opts = {}) {
*/
export async function contentDelete(cwd, nodeId, opts = {}) {
try {
const graph = await loadGraph(cwd);
const graph = await initGraph(cwd);
const result = await deleteContent(graph, nodeId);

if (opts.json) {
Expand Down
8 changes: 4 additions & 4 deletions src/diff.js
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@
* endpoints pass the prefix filter**. No partial cross-prefix edges.
*/

import { loadGraph } from './graph.js';
import { initGraph } from './graph.js';
import { getEpochForRef } from './epoch.js';
import { extractPrefix } from './validators.js';

Expand Down Expand Up @@ -279,7 +279,7 @@ export function collectDiffPositionals(args) {
*/
export async function computeDiff(cwd, refA, refB, opts = {}) {
// 1. Resolve both refs to epochs using a resolver graph
const resolver = await loadGraph(cwd, { writerId: 'diff-resolver' });
const resolver = await initGraph(cwd, { writerId: 'diff-resolver' });

const resultA = await getEpochForRef(resolver, cwd, refA);
if (!resultA) {
Expand Down Expand Up @@ -326,12 +326,12 @@ export async function computeDiff(cwd, refA, refB, opts = {}) {

// 2. Materialize two separate graph instances
const startA = Date.now();
const graphA = await loadGraph(cwd, { writerId: 'diff-a' });
const graphA = await initGraph(cwd, { writerId: 'diff-a' });
await graphA.materialize({ ceiling: tickA });
const msA = Date.now() - startA;

const startB = Date.now();
const graphB = await loadGraph(cwd, { writerId: 'diff-b' });
const graphB = await initGraph(cwd, { writerId: 'diff-b' });
await graphB.materialize({ ceiling: tickB });
const msB = Date.now() - startB;

Expand Down
27 changes: 1 addition & 26 deletions src/edges.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
/**
* @module edges
* Edge creation, querying, and removal for git-mind.
* Edge creation and removal for git-mind.
*/

import { validateEdge } from './validators.js';
Expand Down Expand Up @@ -53,31 +53,6 @@ export async function createEdge(graph, { source, target, type, confidence = 1.0
await patch.commit();
}

/**
* @typedef {object} EdgeQuery
* @property {string} [source] - Filter by source node
* @property {string} [target] - Filter by target node
* @property {string} [type] - Filter by edge type
*/

/**
* Query edges from the graph.
*
* @param {import('@git-stunts/git-warp').default} graph
* @param {EdgeQuery} [filter={}]
* @returns {Promise<Array<{from: string, to: string, label: string, props: object}>>}
*/
export async function queryEdges(graph, filter = {}) {
const allEdges = await graph.getEdges();

return allEdges.filter(edge => {
if (filter.source && edge.from !== filter.source) return false;
if (filter.target && edge.to !== filter.target) return false;
if (filter.type && edge.label !== filter.type) return false;
return true;
});
}

/**
* Remove an edge from the graph.
*
Expand Down
11 changes: 1 addition & 10 deletions src/graph.js
Original file line number Diff line number Diff line change
Expand Up @@ -44,20 +44,11 @@ export async function initGraph(repoPath, opts = {}) {

/**
* Load an existing git-mind graph from a repository.
* @deprecated Use initGraph — WarpGraph.open is idempotent (init and load are the same call).
* @param {string} repoPath - Path to the Git repository
* @param {{ writerId?: string }} [opts]
* @returns {Promise<import('@git-stunts/git-warp').default>}
*/
export async function loadGraph(repoPath, opts = {}) {
// Same operation — WarpGraph.open is idempotent
return initGraph(repoPath, opts);
}

/**
* Save (checkpoint) the graph state.
* @param {import('@git-stunts/git-warp').default} graph
* @returns {Promise<string>} checkpoint SHA
*/
export async function saveGraph(graph) {
return graph.createCheckpoint();
}
6 changes: 3 additions & 3 deletions src/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,9 @@
* Public API for git-mind — a project knowledge graph tool built on git-warp.
*/

export { initGraph, loadGraph, saveGraph } from './graph.js';
export { createEdge, queryEdges, removeEdge, EDGE_TYPES } from './edges.js';
export { getNodes, hasNode, getNode, getNodesByPrefix, setNodeProperty, unsetNodeProperty } from './nodes.js';
export { initGraph, loadGraph } from './graph.js';
export { createEdge, removeEdge, EDGE_TYPES } from './edges.js';
export { getNode, setNodeProperty, unsetNodeProperty } from './nodes.js';
Comment on lines +6 to +8
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧹 Nitpick | 🔵 Trivial

Breaking API surface reduction — ensure migration docs are adequate.

Five exports removed:

  • saveGraph — dead code (per CHANGELOG)
  • queryEdgesgraph.getEdges() with inline filter
  • getNodesgraph.getNodes()
  • hasNodegraph.hasNode(id)
  • getNodesByPrefixgraph.getNodes() + filter(id => id.startsWith(prefix + ':'))

The CHANGELOG documents the migration paths. Confirm the README or API docs are updated for consumers, or consider adding a MIGRATION.md for this release.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/index.js` around lines 6 - 8, The public API was reduced by removing
exports saveGraph, queryEdges, getNodes, hasNode, and getNodesByPrefix; update
consumer docs by adding a concise MIGRATION.md (and update README/API docs) that
lists each removed symbol and the exact migration snippet: replace
queryEdges(...) with graph.getEdges(... /* apply inline filter */), replace
getNodes() with graph.getNodes(), replace hasNode(id) with graph.hasNode(id),
replace getNodesByPrefix(prefix) with graph.getNodes().filter(id =>
id.startsWith(prefix + ':')), and note saveGraph is removed (dead code) with
recommended alternative or confirmation it's unnecessary; reference the
CHANGELOG entry and include minimal example code for each mapping.

export { computeStatus } from './status.js';
export { importFile, importData, parseImportFile, validateImportData } from './import.js';
export { importFromMarkdown, parseFrontmatter } from './frontmatter.js';
Expand Down
Loading
Loading