Skip to content

Commit e50d285

Browse files
authored
Merge pull request #296 from flyingrobots/refactor/warp-api-cleanup
Resolved 6 review issues across 4 rounds; v5.0.0 — breaking API surface removal of 5 pass-through wrappers.
2 parents 122981a + 89d6e36 commit e50d285

17 files changed

Lines changed: 136 additions & 171 deletions

CHANGELOG.md

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,20 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
77

88
## [Unreleased]
99

10+
## [5.0.0] - 2026-02-25
11+
12+
### Breaking
13+
14+
- **`getNodes(graph)` wrapper** — Use `graph.getNodes()` directly (#295)
15+
- **`hasNode(graph, id)` wrapper** — Use `graph.hasNode(id)` directly (#295)
16+
- **`saveGraph(graph)` wrapper** — Dead code with zero call sites (#295)
17+
- **`queryEdges(graph, filter)` wrapper** — Use `graph.getEdges()` with inline filter (#295)
18+
- **`getNodesByPrefix(graph, prefix)` wrapper** — Use `graph.getNodes()` with `startsWith()` filter (#295)
19+
20+
### Changed
21+
22+
- **All internal `loadGraph()` calls replaced with `initGraph()`**`loadGraph` kept as deprecated alias for public API backward compatibility (#295)
23+
1024
## [4.0.1] - 2026-02-22
1125

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

374+
[5.0.0]: https://github.com/neuroglyph/git-mind/releases/tag/v5.0.0
375+
[4.0.1]: https://github.com/neuroglyph/git-mind/releases/tag/v4.0.1
376+
[4.0.0]: https://github.com/neuroglyph/git-mind/releases/tag/v4.0.0
377+
[3.3.0]: https://github.com/neuroglyph/git-mind/releases/tag/v3.3.0
378+
[3.2.0]: https://github.com/neuroglyph/git-mind/releases/tag/v3.2.0
360379
[3.1.0]: https://github.com/neuroglyph/git-mind/releases/tag/v3.1.0
361380
[3.0.0]: https://github.com/neuroglyph/git-mind/releases/tag/v3.0.0
362381
[2.0.0-alpha.5]: https://github.com/neuroglyph/git-mind/releases/tag/v2.0.0-alpha.5

package-lock.json

Lines changed: 2 additions & 2 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "@neuroglyph/git-mind",
3-
"version": "4.0.1",
3+
"version": "5.0.0",
44
"description": "A project knowledge graph tool built on git-warp",
55
"type": "module",
66
"license": "Apache-2.0",

src/cli/commands.js

Lines changed: 32 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -6,9 +6,9 @@
66
import { execFileSync } from 'node:child_process';
77
import { writeFile, chmod, access, constants, readFile } from 'node:fs/promises';
88
import { join, extname } from 'node:path';
9-
import { initGraph, loadGraph } from '../graph.js';
10-
import { createEdge, queryEdges, removeEdge } from '../edges.js';
11-
import { getNodes, getNode, getNodesByPrefix, setNodeProperty, unsetNodeProperty } from '../nodes.js';
9+
import { initGraph } from '../graph.js';
10+
import { createEdge, removeEdge } from '../edges.js';
11+
import { getNode, setNodeProperty, unsetNodeProperty } from '../nodes.js';
1212
import { computeStatus } from '../status.js';
1313
import { importFile } from '../import.js';
1414
import { importFromMarkdown } from '../frontmatter.js';
@@ -62,11 +62,11 @@ export async function resolveContext(cwd, envelope) {
6262
let graph;
6363

6464
if (asOf === 'HEAD') {
65-
graph = await loadGraph(cwd, { writerId: 'ctx-reader' });
65+
graph = await initGraph(cwd, { writerId: 'ctx-reader' });
6666
} else {
6767
// Time-travel: resolve git ref → Lamport tick → materialize
6868
// Use a separate resolver instance so we can materialize a fresh one.
69-
const resolver = await loadGraph(cwd, { writerId: 'ctx-resolver' });
69+
const resolver = await initGraph(cwd, { writerId: 'ctx-resolver' });
7070
const result = await getEpochForRef(resolver, cwd, asOf);
7171
if (!result) {
7272
throw new Error(
@@ -76,7 +76,7 @@ export async function resolveContext(cwd, envelope) {
7676
}
7777
resolvedTick = result.epoch.tick;
7878
// materialize({ ceiling }) is destructive — use a dedicated instance
79-
graph = await loadGraph(cwd, { writerId: 'ctx-temporal' });
79+
graph = await initGraph(cwd, { writerId: 'ctx-temporal' });
8080
await graph.materialize({ ceiling: resolvedTick });
8181
}
8282

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

141141
try {
142-
const graph = await loadGraph(cwd);
142+
const graph = await initGraph(cwd);
143143
await createEdge(graph, { source: src, target: tgt, type, confidence: opts.confidence });
144144
console.log(success(`${src} --[${type}]--> ${tgt}`));
145145
} catch (err) {
@@ -208,8 +208,14 @@ export async function view(cwd, viewSpec, opts = {}) {
208208
*/
209209
export async function list(cwd, filter = {}) {
210210
try {
211-
const graph = await loadGraph(cwd);
212-
const edges = await queryEdges(graph, filter);
211+
const graph = await initGraph(cwd);
212+
const allEdges = await graph.getEdges();
213+
const edges = allEdges.filter(edge => {
214+
if (filter.source && edge.from !== filter.source) return false;
215+
if (filter.target && edge.to !== filter.target) return false;
216+
if (filter.type && edge.label !== filter.type) return false;
217+
return true;
218+
});
213219

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

239245
try {
240-
const graph = await loadGraph(cwd);
246+
const graph = await initGraph(cwd);
241247
await removeEdge(graph, source, target, type);
242248
console.log(success(`Removed: ${source} --[${type}]--> ${target}`));
243249
} catch (err) {
@@ -299,7 +305,7 @@ export async function processCommitCmd(cwd, sha) {
299305
try {
300306
execFileSync('git', ['rev-parse', '--verify', sha], { cwd, encoding: 'utf-8' });
301307
const message = execFileSync('git', ['log', '-1', '--format=%B', sha], { cwd, encoding: 'utf-8' });
302-
const graph = await loadGraph(cwd);
308+
const graph = await initGraph(cwd);
303309
const directives = await processCommit(graph, { sha, message });
304310

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

342348
// List nodes (optionally filtered by prefix)
349+
const allNodes = await graph.getNodes();
343350
const nodeList = opts.prefix
344-
? await getNodesByPrefix(graph, opts.prefix)
345-
: await getNodes(graph);
351+
? allNodes.filter(n => n.startsWith(opts.prefix + ':'))
352+
: allNodes;
346353

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

400407
try {
401-
const graph = await loadGraph(cwd);
408+
const graph = await initGraph(cwd);
402409
const result = await getEpochForRef(graph, cwd, ref);
403410

404411
if (!result) {
@@ -441,7 +448,7 @@ export async function at(cwd, ref, opts = {}) {
441448
*/
442449
export async function importCmd(cwd, filePath, opts = {}) {
443450
try {
444-
const graph = await loadGraph(cwd);
451+
const graph = await initGraph(cwd);
445452
const result = await importFile(graph, filePath, { dryRun: opts.dryRun });
446453

447454
if (opts.json) {
@@ -467,7 +474,7 @@ export async function importCmd(cwd, filePath, opts = {}) {
467474
*/
468475
export async function importMarkdownCmd(cwd, pattern, opts = {}) {
469476
try {
470-
const graph = await loadGraph(cwd);
477+
const graph = await initGraph(cwd);
471478
const result = await importFromMarkdown(graph, cwd, pattern, { dryRun: opts.dryRun });
472479

473480
if (opts.json) {
@@ -534,7 +541,7 @@ export async function mergeCmd(cwd, opts = {}) {
534541
}
535542

536543
try {
537-
const graph = await loadGraph(cwd);
544+
const graph = await initGraph(cwd);
538545
const result = await mergeFromRepo(graph, opts.from, {
539546
repoName: opts.repoName,
540547
dryRun: opts.dryRun,
@@ -594,7 +601,7 @@ export async function doctor(cwd, opts = {}) {
594601
*/
595602
export async function suggest(cwd, opts = {}) {
596603
try {
597-
const graph = await loadGraph(cwd);
604+
const graph = await initGraph(cwd);
598605
const result = await generateSuggestions(cwd, graph, {
599606
agent: opts.agent,
600607
range: opts.context,
@@ -618,7 +625,7 @@ export async function suggest(cwd, opts = {}) {
618625
*/
619626
export async function review(cwd, opts = {}) {
620627
try {
621-
const graph = await loadGraph(cwd);
628+
const graph = await initGraph(cwd);
622629

623630
// Batch mode
624631
if (opts.batch) {
@@ -736,7 +743,7 @@ export async function set(cwd, nodeId, key, value, opts = {}) {
736743
}
737744

738745
try {
739-
const graph = await loadGraph(cwd);
746+
const graph = await initGraph(cwd);
740747
const result = await setNodeProperty(graph, nodeId, key, value);
741748

742749
if (opts.json) {
@@ -769,7 +776,7 @@ export async function unsetCmd(cwd, nodeId, key, opts = {}) {
769776
}
770777

771778
try {
772-
const graph = await loadGraph(cwd);
779+
const graph = await initGraph(cwd);
773780
const result = await unsetNodeProperty(graph, nodeId, key);
774781

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

841-
const graph = await loadGraph(cwd);
848+
const graph = await initGraph(cwd);
842849
const result = await writeContent(graph, nodeId, buf, { mime });
843850

844851
if (opts.json) {
@@ -861,7 +868,7 @@ export async function contentSet(cwd, nodeId, filePath, opts = {}) {
861868
*/
862869
export async function contentShow(cwd, nodeId, opts = {}) {
863870
try {
864-
const graph = await loadGraph(cwd);
871+
const graph = await initGraph(cwd);
865872
const { content, meta } = await readContent(graph, nodeId);
866873

867874
if (opts.json) {
@@ -890,7 +897,7 @@ export async function contentShow(cwd, nodeId, opts = {}) {
890897
*/
891898
export async function contentMeta(cwd, nodeId, opts = {}) {
892899
try {
893-
const graph = await loadGraph(cwd);
900+
const graph = await initGraph(cwd);
894901
const meta = await getContentMeta(graph, nodeId);
895902

896903
if (!meta) {
@@ -921,7 +928,7 @@ export async function contentMeta(cwd, nodeId, opts = {}) {
921928
*/
922929
export async function contentDelete(cwd, nodeId, opts = {}) {
923930
try {
924-
const graph = await loadGraph(cwd);
931+
const graph = await initGraph(cwd);
925932
const result = await deleteContent(graph, nodeId);
926933

927934
if (opts.json) {

src/diff.js

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@
1818
* endpoints pass the prefix filter**. No partial cross-prefix edges.
1919
*/
2020

21-
import { loadGraph } from './graph.js';
21+
import { initGraph } from './graph.js';
2222
import { getEpochForRef } from './epoch.js';
2323
import { extractPrefix } from './validators.js';
2424

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

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

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

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

src/edges.js

Lines changed: 1 addition & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
/**
22
* @module edges
3-
* Edge creation, querying, and removal for git-mind.
3+
* Edge creation and removal for git-mind.
44
*/
55

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

56-
/**
57-
* @typedef {object} EdgeQuery
58-
* @property {string} [source] - Filter by source node
59-
* @property {string} [target] - Filter by target node
60-
* @property {string} [type] - Filter by edge type
61-
*/
62-
63-
/**
64-
* Query edges from the graph.
65-
*
66-
* @param {import('@git-stunts/git-warp').default} graph
67-
* @param {EdgeQuery} [filter={}]
68-
* @returns {Promise<Array<{from: string, to: string, label: string, props: object}>>}
69-
*/
70-
export async function queryEdges(graph, filter = {}) {
71-
const allEdges = await graph.getEdges();
72-
73-
return allEdges.filter(edge => {
74-
if (filter.source && edge.from !== filter.source) return false;
75-
if (filter.target && edge.to !== filter.target) return false;
76-
if (filter.type && edge.label !== filter.type) return false;
77-
return true;
78-
});
79-
}
80-
8156
/**
8257
* Remove an edge from the graph.
8358
*

src/graph.js

Lines changed: 1 addition & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -44,20 +44,11 @@ export async function initGraph(repoPath, opts = {}) {
4444

4545
/**
4646
* Load an existing git-mind graph from a repository.
47+
* @deprecated Use initGraph — WarpGraph.open is idempotent (init and load are the same call).
4748
* @param {string} repoPath - Path to the Git repository
4849
* @param {{ writerId?: string }} [opts]
4950
* @returns {Promise<import('@git-stunts/git-warp').default>}
5051
*/
5152
export async function loadGraph(repoPath, opts = {}) {
52-
// Same operation — WarpGraph.open is idempotent
5353
return initGraph(repoPath, opts);
5454
}
55-
56-
/**
57-
* Save (checkpoint) the graph state.
58-
* @param {import('@git-stunts/git-warp').default} graph
59-
* @returns {Promise<string>} checkpoint SHA
60-
*/
61-
export async function saveGraph(graph) {
62-
return graph.createCheckpoint();
63-
}

src/index.js

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -3,9 +3,9 @@
33
* Public API for git-mind — a project knowledge graph tool built on git-warp.
44
*/
55

6-
export { initGraph, loadGraph, saveGraph } from './graph.js';
7-
export { createEdge, queryEdges, removeEdge, EDGE_TYPES } from './edges.js';
8-
export { getNodes, hasNode, getNode, getNodesByPrefix, setNodeProperty, unsetNodeProperty } from './nodes.js';
6+
export { initGraph, loadGraph } from './graph.js';
7+
export { createEdge, removeEdge, EDGE_TYPES } from './edges.js';
8+
export { getNode, setNodeProperty, unsetNodeProperty } from './nodes.js';
99
export { computeStatus } from './status.js';
1010
export { importFile, importData, parseImportFile, validateImportData } from './import.js';
1111
export { importFromMarkdown, parseFrontmatter } from './frontmatter.js';

0 commit comments

Comments
 (0)