Skip to content
Merged
Show file tree
Hide file tree
Changes from 6 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
23 changes: 23 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,29 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0

## [Unreleased]

## [4.0.1] - 2026-02-22

### Fixed

- **OID guard in `writeContent()`** — Throw if `getContentOid()` returns null after a successful write, enforcing the `WriteContentResult.sha: string` contract (#284)
- **Error cause chain in `readContent()`** — Capture original error via `{ cause: err }` so callers can distinguish blob-not-found from infrastructure failures (#284)
- **Null guard in `readContent()`** — Changed `!contentBuf` to explicit `contentBuf == null` for clearer null/undefined intent (#284)
- **Null guard in `hasContent()`** — Changed `sha !== null` to `sha != null` to catch both null and undefined from `getContentOid()`, consistent with other callers (#284)
- **ROADMAP stale integrity-check language** — Corrected binary content backlog item to reflect WARP-native blob storage; reframed `--verify` flag as OID existence check (#284)
- **JSDoc typedef terminology** — Changed "Git blob SHA" / "Written blob SHA" to "Git blob OID" in `ContentMeta` and `WriteContentResult` typedefs (#284)
- **Dead `execSync` import** — Removed unused `execSync` from `test/content.test.js`; only `execFileSync` is used (#284)

### Added

- **Empty content edge-case test** — Verifies `writeContent()` handles empty string input correctly (size 0, round-trip intact) (#284)

## [4.0.0] - 2026-02-22

### Changed

- **BREAKING: Migrate content system to git-warp native API** — Replaced custom CAS layer (`git hash-object` / `git cat-file`) with `@git-stunts/git-warp` native `setContent()` / `getContent()` API. Content properties now use WARP's `CONTENT_PROPERTY_KEY` instead of custom `_content.sha`. Removes all direct git subprocess calls from content module (#284)
- **Test count** — 577 tests across 29 files (was 571)

## [3.3.0] - 2026-02-22

### Added
Expand Down
6 changes: 3 additions & 3 deletions ROADMAP.md
Original file line number Diff line number Diff line change
Expand Up @@ -2189,9 +2189,9 @@ Two issues were filed during the M12 extension polish pass and intentionally def

### Content system enhancements (from M13 VESSEL review)

- **`git mind content list`** — Query all nodes that have `_content.sha` properties. Currently there's no way to discover which nodes carry content without inspecting each one individually.
- **Binary content support** — Add base64 encoding for non-text MIME types. Currently the content system is text-only (UTF-8); non-UTF-8 blobs fail the integrity check by design. Requires reintroducing encoding metadata and updating `readContent()` to handle buffer round-trips.
- **`content meta --verify` flag** — Run the SHA integrity check without dumping the full content body. Useful for bulk health checks across all content-bearing nodes.
- **`git mind content list`** — Query all nodes that have `_content` properties. Currently there's no way to discover which nodes carry content without inspecting each one individually.
- **Binary content support** — Add base64 encoding for non-text MIME types. Currently the content system is text-only (UTF-8); WARP stores blobs natively but `readContent()` always calls `.toString('utf-8')`, so binary round-trips are lossy. Requires returning a `Buffer` for non-text MIME types and reintroducing encoding metadata.
- **`content meta --verify` flag** — Verify the content blob OID exists in the git object store without dumping the full body. Useful for bulk health checks across all content-bearing nodes.

### Codebase hardening (from M13 VESSEL review)

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": "3.3.0",
"version": "4.0.1",
Copy link
Contributor

@coderabbitai coderabbitai bot Feb 22, 2026

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🔴 Critical

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Verify that the WARP API surface used in this PR is present in the installed version
echo "=== Installed `@git-stunts/git-warp` version ==="
node -e "import('@git-stunts/git-warp').then(m => { console.log('Exports:', Object.keys(m)); console.log('CONTENT_PROPERTY_KEY:', m.CONTENT_PROPERTY_KEY); }).catch(e => console.error(e.message))" 2>&1 || echo "Module import check completed"

echo ""
echo "=== Checking package.json for `@git-stunts/git-warp` version ==="
cat package.json | jq -r '.dependencies["@git-stunts/git-warp"] // .devDependencies["@git-stunts/git-warp"] // "not found"'

echo ""
echo "=== Search for CONTENT_PROPERTY_KEY in warp package ==="
rg "CONTENT_PROPERTY_KEY" node_modules/@git-stunts/git-warp/ -n --include="*.js" -l 2>/dev/null | head -10

echo ""
echo "=== Search for attachContent / getContentOid / getNodeProps ==="
rg "attachContent|getContentOid|getNodeProps\b" node_modules/@git-stunts/git-warp/ -n --include="*.js" -l 2>/dev/null | head -10

echo ""
echo "=== Checking if node_modules/@git-stunts/git-warp exists ==="
ls -la node_modules/@git-stunts/ 2>/dev/null | grep git-warp || echo "@git-stunts/git-warp not in node_modules"

Repository: neuroglyph/git-mind

Length of output: 489


🏁 Script executed:

#!/bin/bash
echo "=== Checking package-lock.json for exact `@git-stunts/git-warp` version ==="
jq -r '.packages."node_modules/@git-stunts/git-warp".version // "not found"' package-lock.json 2>/dev/null || echo "package-lock.json check failed"

echo ""
echo "=== Searching for CONTENT_PROPERTY_KEY usage in codebase ==="
rg "CONTENT_PROPERTY_KEY" --include="*.js" --include="*.mjs" -n -B2 -A2 2>/dev/null | head -30

echo ""
echo "=== Searching for attachContent usage ==="
rg "attachContent\(" --include="*.js" --include="*.mjs" -n -B2 -A2 2>/dev/null | head -30

echo ""
echo "=== Searching for getContent / getContentOid usage ==="
rg "getContent|getContentOid" --include="*.js" --include="*.mjs" -n -B1 -A1 2>/dev/null | head -30

echo ""
echo "=== Searching for getNodeProps usage ==="
rg "getNodeProps" --include="*.js" --include="*.mjs" -n -B1 -A1 2>/dev/null | head -30

echo ""
echo "=== Check what this PR actually modifies ==="
git diff HEAD~1 --name-only 2>/dev/null | head -20

Repository: neuroglyph/git-mind

Length of output: 392


🏁 Script executed:

#!/bin/bash
echo "=== Full package.json ==="
cat -n package.json

echo ""
echo "=== Search for git-warp imports ==="
rg "from\s+['\"]@git-stunts/git-warp|require\(['\"]@git-stunts/git-warp" --include="*.js" --include="*.mjs" -n 2>/dev/null

echo ""
echo "=== Check src/content.js mentioned in learnings ==="
if [ -f src/content.js ]; then
  wc -l src/content.js
  echo "--- First 50 lines ---"
  head -50 src/content.js
fi

echo ""
echo "=== List all .js/.mjs files in src/ ==="
find src -type f \( -name "*.js" -o -name "*.mjs" \) 2>/dev/null | head -20

Repository: neuroglyph/git-mind

Length of output: 3465


🏁 Script executed:

#!/bin/bash
echo "=== Full src/content.js ==="
cat -n src/content.js

echo ""
echo "=== Search for method calls on graph/patch objects ==="
rg "graph\.(getContent|getContentOid|getNodeProps|attachContent)" src/content.js -n 2>/dev/null
rg "patch\.(attachContent)" src/content.js -n 2>/dev/null

Repository: neuroglyph/git-mind

Length of output: 6789


Verify @git-stunts/git-warp@11.5.0 exports the required content API surface.

The content system (src/content.js) actively uses CONTENT_PROPERTY_KEY, patch.attachContent(), graph.getContent(), graph.getContentOid(), and graph.getNodeProps(). All five are called at runtime across writeContent, readContent, getContentMeta, hasContent, and deleteContent. If any of these exports do not exist in v11.5.0, the content subsystem will fail at runtime.

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

In `@package.json` at line 3, Ensure the installed `@git-stunts/git-warp`@11.5.0
actually exports CONTENT_PROPERTY_KEY, patch.attachContent, graph.getContent,
graph.getContentOid, and graph.getNodeProps which src/content.js relies on; to
fix, either bump or pin the dependency in package.json to a version that exports
those symbols, or change the imports in src/content.js to the correct export
names/paths from the library, and add a short runtime guard in src/content.js
that checks for these five exports and throws a clear error listing any missing
symbols so the issue fails fast.

Copy link
Owner Author

Choose a reason for hiding this comment

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

Thanks for flagging! This is a false positive — CodeRabbit sandbox could not resolve node_modules, but the API surface is fully verified locally. All 5 symbols (CONTENT_PROPERTY_KEY, patch.attachContent, graph.getContent, graph.getContentOid, graph.getNodeProps) are exercised by the test suite. 37 content tests pass covering every code path. No action needed.

Copy link
Contributor

Choose a reason for hiding this comment

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

@flyingrobots, thank you for the clarification! You're absolutely right — the sandbox limitation prevented me from verifying the node_modules, but your local test coverage with 37 passing content tests clearly confirms that all five API symbols are present and functioning correctly in @git-stunts/git-warp@11.5.0. I appreciate you taking the time to confirm this. No action needed indeed!


🧠 Learnings used
Learnt from: CR
Repo: neuroglyph/git-mind PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-02-20T17:11:37.241Z
Learning: Applies to {package.json,package-lock.json,**/*.{js,mjs}} : Use `git-stunts/git-warp` as a core dependency for CRDT graph on Git

Learnt from: flyingrobots
Repo: neuroglyph/git-mind PR: 169
File: docs/templates/code-review-feedback.md:26-33
Timestamp: 2025-09-16T17:10:27.477Z
Learning: The user (flyingrobots) uses Obsidian to view/edit Markdown files and prefers to keep Obsidian-specific syntax like collapsible callout markers (e.g., `[!NOTE]-` for collapsible callouts) even if they're not GitHub-compatible.

Learnt from: flyingrobots
Repo: neuroglyph/git-mind PR: 276
File: src/content.js:110-127
Timestamp: 2026-02-22T13:15:58.473Z
Learning: In the git-mind repository, the M13 content system (src/content.js) is intentionally text-only (UTF-8). Non-UTF-8 blobs are designed to fail the integrity check in readContent() as a boundary enforcement mechanism. Binary content support is deferred to a future milestone. The _content.encoding property was deliberately removed.

"description": "A project knowledge graph tool built on git-warp",
"type": "module",
"license": "Apache-2.0",
Expand Down
4 changes: 2 additions & 2 deletions src/cli/commands.js
Original file line number Diff line number Diff line change
Expand Up @@ -838,7 +838,7 @@ export async function contentSet(cwd, nodeId, filePath, opts = {}) {
const mime = opts.mime ?? MIME_MAP[extname(filePath).toLowerCase()] ?? 'application/octet-stream';

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

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

if (opts.json) {
outputJson('content-show', { nodeId, content, ...meta });
Expand Down
129 changes: 46 additions & 83 deletions src/content.js
Original file line number Diff line number Diff line change
@@ -1,66 +1,48 @@
/**
* @module content
* Content-on-node: attach rich content (markdown, text, etc.) to graph nodes
* using git's native content-addressed storage.
* using git-warp's native content-addressed storage.
*
* Content is stored as git blobs via `git hash-object -w`. The blob SHA and
* metadata are recorded as WARP node properties under the `_content.` prefix.
* Content is stored as git blobs via WARP's patch.attachContent(). The blob OID
* and metadata are recorded as WARP node properties.
*
* Property convention:
* _content.sha — git blob SHA
* _content.mime — MIME type (e.g. "text/markdown")
* _content.size — byte count
* _content — git blob OID (managed by WARP via CONTENT_PROPERTY_KEY)
* _content.mime — MIME type (e.g. "text/markdown")
* _content.size — byte count
*/

import { execFileSync } from 'node:child_process';
import { CONTENT_PROPERTY_KEY } from '@git-stunts/git-warp';

/** Property key prefix for content metadata. */
const PREFIX = '_content.';

/** Known content property keys. */
const KEYS = {
sha: `${PREFIX}sha`,
mime: `${PREFIX}mime`,
size: `${PREFIX}size`,
};

/** Validates a string is a 40- or 64-hex-char git object hash (SHA-1 or SHA-256). */
const SHA_RE = /^[0-9a-f]{40,64}$/;

/** @throws {Error} if sha is not a valid git object hash (40 or 64 hex chars). */
function assertValidSha(sha) {
if (typeof sha !== 'string' || !SHA_RE.test(sha)) {
throw new Error(`Invalid content SHA: ${sha}`);
}
}
const MIME_KEY = '_content.mime';
const SIZE_KEY = '_content.size';

/**
* @typedef {object} ContentMeta
* @property {string} sha - Git blob SHA
* @property {string} sha - Git blob OID
* @property {string} mime - MIME type
* @property {number} size - Content size in bytes
*/

/**
* @typedef {object} WriteContentResult
* @property {string} nodeId - Target node
* @property {string} sha - Written blob SHA
* @property {string} sha - Written blob OID
* @property {string} mime - MIME type
* @property {number} size - Byte count
*/

/**
* Write content to a graph node. Stores the content as a git blob and records
* metadata as node properties.
* Write content to a graph node. Stores the content as a git blob via WARP's
* native content API and records metadata as node properties.
*
* @param {string} cwd - Repository working directory
* @param {import('@git-stunts/git-warp').default} graph - WARP graph instance
* @param {string} nodeId - Target node ID
* @param {Buffer|string} content - Content to store
* @param {{ mime?: string }} [opts]
* @returns {Promise<WriteContentResult>}
*/
export async function writeContent(cwd, graph, nodeId, content, opts = {}) {
export async function writeContent(graph, nodeId, content, opts = {}) {
const exists = await graph.hasNode(nodeId);
if (!exists) {
throw new Error(`Node not found: ${nodeId}`);
Expand All @@ -70,70 +52,52 @@ export async function writeContent(cwd, graph, nodeId, content, opts = {}) {
const mime = opts.mime ?? 'text/plain';
const size = buf.length;

// Write blob to git object store
const sha = execFileSync('git', ['hash-object', '-w', '--stdin'], {
cwd,
input: buf,
encoding: 'utf-8',
}).trim();

// Record metadata as node properties
const patch = await graph.createPatch();
patch.setProperty(nodeId, KEYS.sha, sha);
patch.setProperty(nodeId, KEYS.mime, mime);
patch.setProperty(nodeId, KEYS.size, size);
await patch.attachContent(nodeId, buf);
patch.setProperty(nodeId, MIME_KEY, mime);
patch.setProperty(nodeId, SIZE_KEY, size);
await patch.commit();

const sha = await graph.getContentOid(nodeId);

if (sha == null) {
throw new Error(`Failed to retrieve OID after writing content to node: ${nodeId}`);
}

return { nodeId, sha, mime, size };
}

/**
* Read content attached to a graph node. Retrieves the blob from git's object
* store and verifies SHA integrity.
* Read content attached to a graph node. Retrieves the blob from WARP's
* native content store.
*
* @param {string} cwd - Repository working directory
* @param {import('@git-stunts/git-warp').default} graph - WARP graph instance
* @param {string} nodeId - Target node ID
* @returns {Promise<{ content: string, meta: ContentMeta }>}
*/
export async function readContent(cwd, graph, nodeId) {
export async function readContent(graph, nodeId) {
const meta = await getContentMeta(graph, nodeId);
if (!meta) {
throw new Error(`No content attached to node: ${nodeId}`);
}

// Validate SHA before passing to git
assertValidSha(meta.sha);

// Retrieve blob from git object store
let content;
let contentBuf;
try {
content = execFileSync('git', ['cat-file', 'blob', meta.sha], {
cwd,
encoding: 'utf-8',
});
} catch {
contentBuf = await graph.getContent(nodeId);
} catch (err) {
throw new Error(
`Content blob ${meta.sha} not found in git object store for node: ${nodeId}`,
`Failed to retrieve content blob ${meta.sha} for node: ${nodeId}`,
{ cause: err },
);
}

// Verify integrity: re-hash and compare
const verifyBuf = Buffer.from(content, 'utf-8');
const verifySha = execFileSync('git', ['hash-object', '--stdin'], {
cwd,
input: verifyBuf,
encoding: 'utf-8',
}).trim();

if (verifySha !== meta.sha) {
if (contentBuf == null || (contentBuf.length === 0 && meta.size > 0)) {
throw new Error(
`Content integrity check failed for node ${nodeId}: ` +
`expected ${meta.sha}, got ${verifySha}`,
`Failed to retrieve content blob ${meta.sha} for node: ${nodeId}`,
);
}

return { content, meta };
return { content: contentBuf.toString('utf-8'), meta };
}

/**
Expand All @@ -150,14 +114,15 @@ export async function getContentMeta(graph, nodeId) {
throw new Error(`Node not found: ${nodeId}`);
}

const propsMap = await graph.getNodeProps(nodeId);
const sha = propsMap?.get(KEYS.sha) ?? null;
const sha = await graph.getContentOid(nodeId);
if (!sha) return null;

const propsMap = await graph.getNodeProps(nodeId);

return {
sha,
mime: propsMap.get(KEYS.mime) ?? 'text/plain',
size: propsMap.get(KEYS.size) ?? 0,
mime: propsMap?.get(MIME_KEY) ?? 'text/plain',
size: propsMap?.get(SIZE_KEY) ?? 0,
};
}

Expand All @@ -172,13 +137,12 @@ export async function hasContent(graph, nodeId) {
const exists = await graph.hasNode(nodeId);
if (!exists) return false;

const propsMap = await graph.getNodeProps(nodeId);
const sha = propsMap?.get(KEYS.sha) ?? null;
return sha !== null;
const sha = await graph.getContentOid(nodeId);
return sha != null;
}

/**
* Delete content from a node by clearing the `_content.*` properties.
* Delete content from a node by clearing the content properties.
* The git blob remains in the object store (cleaned up by git gc).
*
* @param {import('@git-stunts/git-warp').default} graph - WARP graph instance
Expand All @@ -191,17 +155,16 @@ export async function deleteContent(graph, nodeId) {
throw new Error(`Node not found: ${nodeId}`);
}

const propsMap = await graph.getNodeProps(nodeId);
const previousSha = propsMap?.get(KEYS.sha) ?? null;
const previousSha = await graph.getContentOid(nodeId);

if (!previousSha) {
return { nodeId, removed: false, previousSha: null };
}

const patch = await graph.createPatch();
patch.setProperty(nodeId, KEYS.sha, null);
patch.setProperty(nodeId, KEYS.mime, null);
patch.setProperty(nodeId, KEYS.size, null);
patch.setProperty(nodeId, CONTENT_PROPERTY_KEY, null);
patch.setProperty(nodeId, MIME_KEY, null);
patch.setProperty(nodeId, SIZE_KEY, null);
await patch.commit();
Comment on lines 164 to 168
Copy link
Contributor

@coderabbitai coderabbitai bot Feb 22, 2026

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🔴 Critical

🧩 Analysis chain

🏁 Script executed:

# First, check if the WARP library is available in the repository
find . -name "git-warp*" -type d 2>/dev/null | head -10

Repository: neuroglyph/git-mind

Length of output: 45


🏁 Script executed:

# Also search the codebase for WARP API usage patterns
rg "\.attachContent\(|\.setProperty\(|\.getContentOid\(|deleteContent" --include="*.js" -B 2 -A 2 2>/dev/null | head -60

Repository: neuroglyph/git-mind

Length of output: 45


🏁 Script executed:

# Search for any documentation or types that might indicate WARP's content detach mechanisms
rg "detach|removeContent|deleteContent" --include="*.js" --include="*.md" --include="*.json" -B 2 -A 2 2>/dev/null | head -80

Repository: neuroglyph/git-mind

Length of output: 45


🏁 Script executed:

# Check package.json for the exact version of `@git-stunts/git-warp` being used
cat package.json | grep -A 5 -B 5 "@git-stunts/git-warp" 2>/dev/null || echo "WARP dependency not found in package.json"

Repository: neuroglyph/git-mind

Length of output: 379


🏁 Script executed:

wc -l src/content.js

Repository: neuroglyph/git-mind

Length of output: 81


🏁 Script executed:

cat -n src/content.js | sed -n '150,175p'

Repository: neuroglyph/git-mind

Length of output: 645


🏁 Script executed:

# Search for the deleteContent function definition
rg "deleteContent\s*=" src/content.js -B 5 -A 20

Repository: neuroglyph/git-mind

Length of output: 45


🏁 Script executed:

# Check for any imports or references to WARP patch methods
rg "import|require" src/content.js | head -20

Repository: neuroglyph/git-mind

Length of output: 518


🌐 Web query:

@git-stunts/git-warp v11 setProperty null deletion API documentation

💡 Result:

In @git-stunts/git-warp v11 there is no “delete/unset property” operation in the patch API—PatchBuilderV2 / PatchSession only expose .setProperty(nodeId, key, value) (and .setEdgeProperty(...)) plus node/edge add/remove. There is no .removeProperty()/.unsetProperty() method. [1][2]

What you can do:

  • Store null as the property value: property values are documented as JSON-serializable, explicitly including null, and setProperty(..., null) is valid. This does not remove the key; it sets the value to null, so you treat null as your app-level “deleted” sentinel (and you can query for it, e.g. .where({ status: null })). [3]
  • If you truly need to wipe properties, the API-supported way is to remove (tombstone) the entire node/edge (e.g. .removeNode(nodeId) or .removeEdge(...)), which makes its properties invisible as part of deletion semantics—but that’s entity deletion, not per-key deletion. [1][3]

So: setProperty(..., null) is supported, but it’s not a deletion API—it’s just setting the value to null. [2][3]

Sources:
[1] JSR docs: PatchBuilderV2 API (methods list incl. setProperty, no remove/unset)
[2] JSR package types (index.d.ts): setProperty(nodeId, key, value: unknown) (no remove/unset)
[3] GitHub GUIDE.md (raw): property values JSON-serializable incl. null; examples of querying null


🌐 Web query:

git-warp WARP detachContent removeContent API reference

💡 Result:

I couldn’t find any detachContent / removeContent APIs in the Rust git-warp crate docs (the public API is organized into modules like config, cow, git, terminal, etc., and those symbols don’t appear there). [1]

The closest match for DetachContent and RemoveContent (same names, different casing) is in Brave’s ai_chat::AssociatedContentManager (C++):

  • void RemoveContent(AssociatedContentDelegate* delegate, bool notify_updated = true); [2]
  • void DetachContent(); (private helper) [2]
  • Implementation references show DetachContent() being called to clear existing associated content, and RemoveContent(...) being used to remove items (including archived content cleanup). [3]

Sources: [1] [2] [3]


🏁 Script executed:

rg "getContentOid" src/ --include="*.js" -B 3 -A 10

Repository: neuroglyph/git-mind

Length of output: 504


🏁 Script executed:

# Also check if there's any documentation or comments about null handling
rg "CONTENT_PROPERTY_KEY" src/ --include="*.js" -B 2 -A 2

Repository: neuroglyph/git-mind

Length of output: 504


🏁 Script executed:

rg "getContentOid" src/ -B 3 -A 10

Repository: neuroglyph/git-mind

Length of output: 2513


🏁 Script executed:

rg "CONTENT_PROPERTY_KEY" src/ -B 2 -A 2

Repository: neuroglyph/git-mind

Length of output: 778


setProperty(CONTENT_PROPERTY_KEY, null) does not delete the property; it stores the literal value null.

WARP v11 has no removeProperty() API. The setProperty(key, null) call stores null as the property value rather than deleting the key. This means deleteContent returns removed: true while the property still exists (with null value), creating a semantic correctness failure. The only true deletion in WARP is via removeNode() or removeEdge() for entity-level removal. Either use node removal for actual deletion, or reconsider the function's contract and ensure callers understand that properties are set to null rather than removed.

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

In `@src/content.js` around lines 159 - 163, The current deleteContent
implementation calls patch.setProperty(nodeId, CONTENT_PROPERTY_KEY, null) (and
similarly for MIME_KEY and SIZE_KEY), which stores literal null rather than
removing the property; change the implementation to either perform node-level
removal using graph.removeNode(nodeId) when you intend to fully delete the
entity, or adjust deleteContent's contract and callers to treat the operation as
“set properties to null” (i.e., document that setProperty(..., null) is used
instead of removal and do not expect the key to be absent); update references to
setProperty, CONTENT_PROPERTY_KEY, MIME_KEY, SIZE_KEY and use
graph.removeNode/removeEdge where true deletion is required.

Copy link
Owner Author

Choose a reason for hiding this comment

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

Thanks for the deep investigation! This is a known WARP v11 behavior pattern documented in our project notes. WARP v11 has no removeProperty() API — setProperty(key, null) is the established deletion sentinel. All our readers check values (truthiness/nullish), never key presence: getContentOid() returns null after deletion, hasContent() returns false, getContentMeta() returns null. The full delete/re-check cycle is covered by the deleteContent removes properties test. No action needed.

Copy link
Contributor

Choose a reason for hiding this comment

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

@flyingrobots, thank you for the clarification! I appreciate you explaining the intentional design pattern. I've noted that WARP v11's setProperty(key, null) is the established deletion sentinel and that all reader functions check values rather than key presence. This makes the implementation correct as-is.


return { nodeId, removed: true, previousSha };
Expand Down
Loading