🎩 You're Invited:Meet the Socket team at Black Hat in Las Vegas, August 3-6.RSVP
Sign In

@nodemint/projectmind

Package Overview
Dependencies
Maintainers
1
Versions
10
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

@nodemint/projectmind - npm Package Compare versions

Comparing version
0.4.4
to
0.5.0
+36
-0
CHANGELOG.md

@@ -6,2 +6,38 @@ # Changelog

## [0.5.0] - 2026-07-04
### Added
- **Universal digest embedding — the real fix for "the agent didn't call
mind_digest."** `projectmind setup` now embeds the live digest content
directly into each agent's rules file (`CLAUDE.md`, `.cursorrules`,
`.windsurfrules`, `GEMINI.md`, `AGENTS.md`,
`.github/copilot-instructions.md`), between
`<!-- projectmind:digest:begin/end -->` markers, instead of only telling the
agent to call a tool. Rules files are loaded into every agent's context
unconditionally — no model choice involved — so this works identically
across every supported agent, not just Claude Code.
- `save()` now keeps every already-set-up rules file's embedded digest in
sync automatically on every map change (`mind_update`, the CLI, the git
post-commit hook, `projectmind watch` — anything that calls `save()`).
- New exports: `embedDigestBlock`, `committedDigest`, `RULES_FILES`,
`RULES_MARKER_BEGIN/END`, `DIGEST_BLOCK_BEGIN/END`.
### Fixed
- A stray pair of literal null bytes in `dedupEdges`'s dedup key (an old typo:
`\0` was meant as a delimiter but had been written as a raw byte) made
`src/core/index.js` register as a binary file to some tools (e.g. `grep`
without `-a`). Functionally harmless — replaced with a plain space.
### Security / privacy
- Only the **repo-committed** map is ever embedded in rules files —
`committedDigest()` never includes the gitignored local overlay, so
personal handoff notes or local-only nodes can never leak into a file you
commit. Covered by a dedicated regression test.
**Why this matters:** MCP tool descriptions and rules-file instructions are
nudges — no server can force a model to call a specific tool, and real
dogfooding showed a connected server + explicit instructions still wasn't
enough on plain orientation questions. Embedding the actual content sidesteps
that entirely: there's nothing left for the model to choose *not* to do.
## [0.4.4] - 2026-07-04

@@ -8,0 +44,0 @@

+1
-1
{
"name": "@nodemint/projectmind",
"version": "0.4.4",
"version": "0.5.0",
"mcpName": "io.github.Nodemint-dev/projectmind",

@@ -5,0 +5,0 @@ "publishConfig": {

@@ -221,8 +221,25 @@ # projectmind

## One-command agent wiring
## One-command agent wiring — and it doesn't depend on the model choosing to read it
`projectmind setup` idempotently writes the MCP config **and** a short workflow
`projectmind setup` idempotently writes the MCP config **and** a workflow
rules block for each agent — merging into existing configs, never clobbering
(an unparseable config is backed up and skipped):
(an unparseable config is backed up and skipped).
Here's the part that matters: MCP tools are a *nudge* — no server can force a
model to call one, so an agent can (and sometimes will) skip `mind_digest` on
a plain "explain this project" question, especially mid-task. But rules files
(`CLAUDE.md`, `.cursorrules`, `.windsurfrules`, `GEMINI.md`, `AGENTS.md`,
`.github/copilot-instructions.md`) are loaded into every agent's context
**unconditionally, with zero model choice involved** — that's the actual
mechanism, not a coincidence. So `setup` doesn't just write instructions to
call `mind_digest`; it **embeds the live digest itself** between
`<!-- projectmind:digest:begin/end -->` markers, and every subsequent
`mind_update`, CLI edit, git-commit hook run, or `watch` save **re-syncs it
automatically**. The project map is present in context from message one, on
every agent, with no tool call required at all — the same reliability
class as a context-injecting hook, without needing one.
(Only the *repo-committed* map is ever embedded — never your gitignored local
overlay or handoff notes, so nothing personal leaks into a file you commit.)
| Agent | MCP config | Rules file |

@@ -229,0 +246,0 @@ |-------|-----------|------------|

@@ -275,3 +275,3 @@ // projectmind core — the only module that touches map.json.

for (const e of edges) {
const k = `${e.from}${e.to}${e.rel}`;
const k = `${e.from} ${e.to} ${e.rel}`;
if (seen.has(k)) continue;

@@ -298,4 +298,13 @@ seen.add(k);

atomicWrite(mapPath(r), serialize(m));
const digestText = buildDigest(m, loadConfig(r).digest);
// digest.md reflects the committed (repo) map so PR diffs stay clean.
atomicWrite(digestPath(r), buildDigest(m, loadConfig(r).digest));
atomicWrite(digestPath(r), digestText);
// Keep every already-set-up agent's rules file (CLAUDE.md, etc.) carrying
// the current digest inline. This is the universal, cross-agent fix for
// "the model didn't choose to call mind_digest": rules files are loaded
// into context by every agent unconditionally, with zero model choice
// involved, so embedding the actual content there (not just an
// instruction to fetch it) guarantees fresh orientation context without
// depending on the model deciding to call a tool.
syncRuleDigests(r, digestText);
}

@@ -306,2 +315,54 @@ return m;

// ---------------------------------------------------------------------------
// Rules-file digest embedding. Only files that already opted in via
// `projectmind setup` (i.e. already contain RULES_MARKER_BEGIN) are ever
// touched — this never spontaneously creates a rules file for an agent the
// user hasn't set up, and never embeds anything before the user has run
// setup once.
// ---------------------------------------------------------------------------
export const RULES_MARKER_BEGIN = "<!-- projectmind:begin -->";
export const RULES_MARKER_END = "<!-- projectmind:end -->";
export const DIGEST_BLOCK_BEGIN = "<!-- projectmind:digest:begin -->";
export const DIGEST_BLOCK_END = "<!-- projectmind:digest:end -->";
export const RULES_FILES = [
"CLAUDE.md", ".cursorrules", ".windsurfrules", "GEMINI.md", "AGENTS.md",
path.join(".github", "copilot-instructions.md"),
];
// Pure: given a rules file's current content and the digest text to embed,
// return the updated content. No-op if the static instructions block isn't
// present (agent never set up) or malformed (defensive; should not happen).
export function embedDigestBlock(content, digestText) {
if (!content.includes(RULES_MARKER_BEGIN)) return content;
const block = `${DIGEST_BLOCK_BEGIN}\n${digestText.trim()}\n${DIGEST_BLOCK_END}`;
if (content.includes(DIGEST_BLOCK_BEGIN) && content.includes(DIGEST_BLOCK_END)) {
const start = content.indexOf(DIGEST_BLOCK_BEGIN);
const end = content.indexOf(DIGEST_BLOCK_END) + DIGEST_BLOCK_END.length;
return content.slice(0, start) + block + content.slice(end);
}
const markerEndIdx = content.indexOf(RULES_MARKER_END);
if (markerEndIdx === -1) return content;
const insertAt = markerEndIdx + RULES_MARKER_END.length;
return content.slice(0, insertAt) + `\n\n${block}` + content.slice(insertAt);
}
// The digest text safe to embed in COMMITTED rules files — repo scope only,
// never merged with the local overlay (which may carry personal handoff
// notes). Same content as digest.md.
export function committedDigest(r = root()) {
return buildDigest(loadScope("repo", r), loadConfig(r).digest);
}
function syncRuleDigests(r, digestText) {
for (const relFile of RULES_FILES) {
const file = path.join(r, relFile);
let content;
try { content = fs.readFileSync(file, "utf8"); } catch { continue; }
const updated = embedDigestBlock(content, digestText);
if (updated !== content) {
try { fs.writeFileSync(file, updated); } catch { /* best effort */ }
}
}
}
// ---------------------------------------------------------------------------
// Init

@@ -308,0 +369,0 @@ // ---------------------------------------------------------------------------

@@ -182,3 +182,3 @@ #!/usr/bin/env node

const server = new Server(
{ name: "projectmind", version: "0.4.4" },
{ name: "projectmind", version: "0.5.0" },
{ capabilities: { tools: {} } }

@@ -185,0 +185,0 @@ );

@@ -16,2 +16,3 @@ // Multi-agent wiring. Writes the MCP server config and a workflow rules block

import { execFileSync } from "node:child_process";
import { committedDigest, embedDigestBlock, RULES_MARKER_BEGIN, RULES_MARKER_END } from "../core/index.js";

@@ -22,12 +23,10 @@ // `mcp` is a subcommand of the main bin, so plain npx works (same invocation

const RULES_BEGIN = "<!-- projectmind:begin -->";
const RULES_END = "<!-- projectmind:end -->";
const RULES_BODY = [
RULES_BEGIN,
RULES_MARKER_BEGIN,
"## projectmind",
"Before running ls/find/glob/grep or reading files to explain, describe, or orient in this project (e.g. \"what is this project\", \"explain this codebase\", \"how is this structured\"), call `mind_digest` first — it answers most of that in a few hundred tokens.",
"The current project map is embedded below (auto-synced on every change — do not hand-edit between the digest markers). Use it instead of ls/find/glob/grep when you need to explain, describe, or orient in this project.",
"Use `mind_context({ files })` for a task-scoped subgraph, or `mind_query(<id>)` for one module's files/notes.",
"After a structural change, architectural decision, or newly learned convention, call `mind_update` (only the fields that changed).",
"Before the session ends (or when context is about to be compacted), call `mind_handoff` with a one-line note on what's in progress and what's next — it leads the next session's digest.",
RULES_END,
RULES_MARKER_END,
].join("\n");

@@ -91,11 +90,20 @@

function appendRulesBlock(r, relFile) {
// Appends the static instructions block if missing, then (re)embeds the
// current digest between digest markers regardless — so the very first
// setup run already carries real content, not just an instruction to fetch
// it, and every later setup run refreshes a possibly-stale embedded digest.
function appendRulesBlock(r, relFile, digestText) {
const file = path.join(r, relFile);
let content = "";
try { content = fs.readFileSync(file, "utf8"); } catch { /* none */ }
if (content.includes(RULES_BEGIN)) return { file: relFile, status: "already" };
const sep = content && !content.endsWith("\n") ? "\n\n" : content ? "\n" : "";
let original = "";
try { original = fs.readFileSync(file, "utf8"); } catch { /* none */ }
const already = original.includes(RULES_MARKER_BEGIN);
let content = original;
if (!already) {
const sep = original && !original.endsWith("\n") ? "\n\n" : original ? "\n" : "";
content = `${original}${sep}${RULES_BODY}\n`;
}
const updated = embedDigestBlock(content, digestText);
fs.mkdirSync(path.dirname(file), { recursive: true });
fs.writeFileSync(file, `${content}${sep}${RULES_BODY}\n`);
return { file: relFile, status: content ? "appended" : "created" };
fs.writeFileSync(file, updated);
return { file: relFile, status: already ? "already" : (original ? "appended" : "created") };
}

@@ -106,2 +114,3 @@

const keys = agents === "all" || !agents ? SUPPORTED_AGENTS : [].concat(agents).filter((k) => AGENTS[k]);
const digestText = committedDigest(r);
const results = [];

@@ -112,3 +121,3 @@ for (const key of keys) {

if (a.json) results.push({ agent: key, label: a.label, ...mergeJsonConfig(path.join(r, a.json.file), a.json.key, a.json.file) });
if (a.rules) results.push({ agent: key, label: a.label, ...appendRulesBlock(r, a.rules) });
if (a.rules) results.push({ agent: key, label: a.label, ...appendRulesBlock(r, a.rules, digestText) });
}

@@ -115,0 +124,0 @@ return results;