gnosys 5.15.2 → 5.16.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.js +26 -3
- package/dist/lib/cleanup.js +2 -17
- package/dist/lib/paths.d.ts +1 -0
- package/dist/lib/paths.js +29 -0
- package/dist/lib/resolver.js +11 -1
- package/dist/lib/rulesGen.js +2 -2
- package/dist/lib/setupSyncProjectsCommand.js +36 -12
- package/dist/lib/syncCommand.js +8 -1
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -624,7 +624,7 @@ regTool("gnosys_list", "List memories across all stores, optionally filtered by
|
|
|
624
624
|
}
|
|
625
625
|
});
|
|
626
626
|
// ─── Tool: gnosys_add ────────────────────────────────────────────────────
|
|
627
|
-
regTool("gnosys_add", "
|
|
627
|
+
regTool("gnosys_add", "DO NOT USE if you are an LLM agent — call gnosys_add_structured instead (you structure the memory yourself; no redundant server-side LLM call). This freeform tool is REJECTED by default over MCP and only works when the operator sets GNOSYS_ALLOW_FREEFORM_ADD=1 for genuine non-agent callers (scripts, cron). Writes to the project store by default. Use store='personal' for cross-project knowledge, or store='global' to explicitly write to shared org knowledge.", {
|
|
628
628
|
input: z
|
|
629
629
|
.string()
|
|
630
630
|
.describe("Raw text input. Can be a decision, concept, fact, observation, or any knowledge."),
|
|
@@ -642,6 +642,29 @@ regTool("gnosys_add", "Add a new memory from raw text — the server's LLM struc
|
|
|
642
642
|
.describe("Epistemic trust level"),
|
|
643
643
|
projectRoot: projectRootParam,
|
|
644
644
|
}, async ({ input, store: targetStore, author, authority, projectRoot }) => {
|
|
645
|
+
// Hard gate (2026-07-04 decision): every MCP caller in practice is an
|
|
646
|
+
// LLM agent, and agents must use gnosys_add_structured — the freeform
|
|
647
|
+
// path burns a second server-side LLM call and silently fails without
|
|
648
|
+
// a provider key. Reject by default with a redirect the agent can act
|
|
649
|
+
// on immediately. Genuine non-agent MCP callers opt in via env.
|
|
650
|
+
if (process.env.GNOSYS_ALLOW_FREEFORM_ADD !== "1") {
|
|
651
|
+
return {
|
|
652
|
+
content: [
|
|
653
|
+
{
|
|
654
|
+
type: "text",
|
|
655
|
+
text: "gnosys_add is disabled for LLM agents. Call gnosys_add_structured instead — " +
|
|
656
|
+
"structure the memory yourself and pass: title (string), category " +
|
|
657
|
+
"(architecture|decisions|requirements|concepts|roadmap|landscape|open-questions), " +
|
|
658
|
+
"tags (object of string arrays, e.g. { domain: ['auth'], type: ['decision'] }), " +
|
|
659
|
+
"content (markdown body), relevance (space-separated keyword cloud for discovery), " +
|
|
660
|
+
"plus the same store/author/authority/projectRoot values you passed here. " +
|
|
661
|
+
"Retry now with gnosys_add_structured using the same input. " +
|
|
662
|
+
"(Non-agent scripts can re-enable this tool with GNOSYS_ALLOW_FREEFORM_ADD=1.)",
|
|
663
|
+
},
|
|
664
|
+
],
|
|
665
|
+
isError: true,
|
|
666
|
+
};
|
|
667
|
+
}
|
|
645
668
|
const ctx = await resolveToolContext(projectRoot);
|
|
646
669
|
const writeTarget = ctx.resolver.getWriteTarget(targetStore || undefined);
|
|
647
670
|
if (!writeTarget) {
|
|
@@ -972,7 +995,7 @@ regTool("gnosys_init", "Initialize Gnosys in a project directory. Creates .gnosy
|
|
|
972
995
|
content: [
|
|
973
996
|
{
|
|
974
997
|
type: "text",
|
|
975
|
-
text: `Gnosys store ${action} at ${storePath}\n\nProject Identity:\n- ID: ${identity.projectId}\n- Name: ${identity.projectName}\n- Directory: ${identity.workingDirectory}\n- Agent rules target: ${identity.agentRulesTarget || "none detected"}\n- Central DB: ${centralDb?.isAvailable() ? "registered ✓" : "not available"}\n\n${isResync ? "Identity re-synced." : "Created:\n- gnosys.json (project identity)\n- .config/ (internal config)\n- tags.json (tag registry)"}${hookInfo}\n\nThe store is ready. Use gnosys_discover to find existing memories or
|
|
998
|
+
text: `Gnosys store ${action} at ${storePath}\n\nProject Identity:\n- ID: ${identity.projectId}\n- Name: ${identity.projectName}\n- Directory: ${identity.workingDirectory}\n- Agent rules target: ${identity.agentRulesTarget || "none detected"}\n- Central DB: ${centralDb?.isAvailable() ? "registered ✓" : "not available"}\n\n${isResync ? "Identity re-synced." : "Created:\n- gnosys.json (project identity)\n- .config/ (internal config)\n- tags.json (tag registry)"}${hookInfo}\n\nThe store is ready. Use gnosys_discover to find existing memories or gnosys_add_structured to create new ones.`,
|
|
976
999
|
},
|
|
977
1000
|
],
|
|
978
1001
|
};
|
|
@@ -2216,7 +2239,7 @@ regTool("gnosys_stores", "Debug tool — lists all detected Gnosys stores across
|
|
|
2216
2239
|
// Usage hint
|
|
2217
2240
|
lines.push("USAGE:");
|
|
2218
2241
|
lines.push(" Pass projectRoot to any tool to target a specific project:");
|
|
2219
|
-
lines.push(' e.g.
|
|
2242
|
+
lines.push(' e.g. gnosys_add_structured({ projectRoot: "/path/to/my-project", ... })');
|
|
2220
2243
|
return { content: [{ type: "text", text: lines.join("\n") }] };
|
|
2221
2244
|
}
|
|
2222
2245
|
catch (err) {
|
package/dist/lib/cleanup.js
CHANGED
|
@@ -21,28 +21,13 @@
|
|
|
21
21
|
import fs from "fs/promises";
|
|
22
22
|
import fsSync from "fs";
|
|
23
23
|
import path from "path";
|
|
24
|
-
import os from "os";
|
|
25
24
|
import { createInterface } from "readline/promises";
|
|
26
25
|
import { stdin, stdout } from "process";
|
|
27
|
-
import { getProjectRegistryPath } from "./paths.js";
|
|
26
|
+
import { getProjectRegistryPath, isTempProjectPath } from "./paths.js";
|
|
28
27
|
import { safeQuestion } from "./setup/ui/safePrompt.js";
|
|
29
28
|
import { Header } from "./setup/ui/header.js";
|
|
30
29
|
import { Status } from "./setup/ui/status.js";
|
|
31
30
|
import { c, color, glyph } from "./setup/ui/tokens.js";
|
|
32
|
-
const TEMP_PREFIXES = [
|
|
33
|
-
"/tmp/",
|
|
34
|
-
"/private/tmp/",
|
|
35
|
-
"/var/folders/",
|
|
36
|
-
"/private/var/folders/",
|
|
37
|
-
];
|
|
38
|
-
function isTempPath(p) {
|
|
39
|
-
const resolved = path.resolve(p);
|
|
40
|
-
if (TEMP_PREFIXES.some((prefix) => resolved.startsWith(prefix)))
|
|
41
|
-
return true;
|
|
42
|
-
// Honor the system tmpdir too (covers $TMPDIR overrides on CI etc.).
|
|
43
|
-
const sysTmp = path.resolve(os.tmpdir()) + path.sep;
|
|
44
|
-
return resolved.startsWith(sysTmp);
|
|
45
|
-
}
|
|
46
31
|
function isAlive(p) {
|
|
47
32
|
try {
|
|
48
33
|
const stat = fsSync.statSync(path.join(p, ".gnosys"));
|
|
@@ -74,7 +59,7 @@ export async function classifyRegistryEntries() {
|
|
|
74
59
|
const dead = [];
|
|
75
60
|
const temp = [];
|
|
76
61
|
for (const e of entries) {
|
|
77
|
-
if (
|
|
62
|
+
if (isTempProjectPath(e)) {
|
|
78
63
|
temp.push(e);
|
|
79
64
|
}
|
|
80
65
|
else if (isAlive(e)) {
|
package/dist/lib/paths.d.ts
CHANGED
package/dist/lib/paths.js
CHANGED
|
@@ -23,6 +23,7 @@
|
|
|
23
23
|
* reproduce the `path.join(os.homedir(), ".gnosys")` pattern inline.
|
|
24
24
|
*/
|
|
25
25
|
import path from "path";
|
|
26
|
+
import os from "os";
|
|
26
27
|
export function getGnosysHome() {
|
|
27
28
|
if (process.env.GNOSYS_HOME)
|
|
28
29
|
return process.env.GNOSYS_HOME;
|
|
@@ -45,6 +46,13 @@ export function getSandboxDir() {
|
|
|
45
46
|
function getConfigDir() {
|
|
46
47
|
if (process.env.GNOSYS_CONFIG_DIR)
|
|
47
48
|
return process.env.GNOSYS_CONFIG_DIR;
|
|
49
|
+
// GNOSYS_HOME redirects "everything gnosys-owned" (see header comment).
|
|
50
|
+
// Before this, tests that set GNOSYS_HOME still wrote the project
|
|
51
|
+
// registry into the user's REAL ~/.config/gnosys/projects.json — every
|
|
52
|
+
// test run leaked hundreds of /var/folders/... temp entries into it.
|
|
53
|
+
if (process.env.GNOSYS_HOME) {
|
|
54
|
+
return path.join(process.env.GNOSYS_HOME, "config");
|
|
55
|
+
}
|
|
48
56
|
const home = process.env.HOME || process.env.USERPROFILE || "/tmp";
|
|
49
57
|
return path.join(home, ".config", "gnosys");
|
|
50
58
|
}
|
|
@@ -65,3 +73,24 @@ export function getProjectRegistryPath() {
|
|
|
65
73
|
export function getMachineConfigPath() {
|
|
66
74
|
return path.join(getConfigDir(), "machine.json");
|
|
67
75
|
}
|
|
76
|
+
/**
|
|
77
|
+
* True when a project path lives under a system temp location (/tmp,
|
|
78
|
+
* /var/folders, or os.tmpdir()). Such paths are transient — test
|
|
79
|
+
* scaffolding, throwaway scratch dirs — and must never be treated as
|
|
80
|
+
* durable registered projects. Shared by cleanup, registration, and
|
|
81
|
+
* sync-projects so all three agree on what "temp" means.
|
|
82
|
+
*/
|
|
83
|
+
const TEMP_PATH_PREFIXES = [
|
|
84
|
+
"/tmp/",
|
|
85
|
+
"/private/tmp/",
|
|
86
|
+
"/var/folders/",
|
|
87
|
+
"/private/var/folders/",
|
|
88
|
+
];
|
|
89
|
+
export function isTempProjectPath(p) {
|
|
90
|
+
const resolved = path.resolve(p);
|
|
91
|
+
if (TEMP_PATH_PREFIXES.some((prefix) => resolved.startsWith(prefix))) {
|
|
92
|
+
return true;
|
|
93
|
+
}
|
|
94
|
+
const sysTmp = path.resolve(os.tmpdir()) + path.sep;
|
|
95
|
+
return resolved.startsWith(sysTmp);
|
|
96
|
+
}
|
package/dist/lib/resolver.js
CHANGED
|
@@ -9,7 +9,7 @@
|
|
|
9
9
|
*/
|
|
10
10
|
import fs from "fs/promises";
|
|
11
11
|
import path from "path";
|
|
12
|
-
import { getProjectRegistryPath } from "./paths.js";
|
|
12
|
+
import { getProjectRegistryPath, isTempProjectPath } from "./paths.js";
|
|
13
13
|
import { GnosysStore } from "./store.js";
|
|
14
14
|
/**
|
|
15
15
|
* v5.9.1 (#98): read just the projectId from a project's gnosys.json
|
|
@@ -273,6 +273,16 @@ export class GnosysResolver {
|
|
|
273
273
|
*/
|
|
274
274
|
async registerProject(projectDir) {
|
|
275
275
|
const configPath = this.getRegistryPath();
|
|
276
|
+
// Never register temp-dir projects (/tmp, /var/folders, os.tmpdir())
|
|
277
|
+
// in the user's REAL registry — those are test scaffolding or scratch
|
|
278
|
+
// dirs that vanish, then linger as stale entries `gnosys setup
|
|
279
|
+
// sync-projects` counts forever. When GNOSYS_HOME / GNOSYS_CONFIG_DIR
|
|
280
|
+
// redirects the registry (tests, sandboxes), temp paths are allowed,
|
|
281
|
+
// since the whole registry is itself transient.
|
|
282
|
+
const registryIsIsolated = Boolean(process.env.GNOSYS_HOME) || Boolean(process.env.GNOSYS_CONFIG_DIR);
|
|
283
|
+
if (!registryIsIsolated && isTempProjectPath(projectDir)) {
|
|
284
|
+
return;
|
|
285
|
+
}
|
|
276
286
|
let projects = [];
|
|
277
287
|
try {
|
|
278
288
|
const raw = await fs.readFile(configPath, "utf-8");
|
package/dist/lib/rulesGen.js
CHANGED
|
@@ -35,7 +35,7 @@ This project uses **Gnosys** for persistent memory via MCP. Gnosys uses a centra
|
|
|
35
35
|
|
|
36
36
|
### Write automatically
|
|
37
37
|
|
|
38
|
-
- **Always write with \`gnosys_add_structured\`** — you are an LLM, so structure the memory yourself (title, category, tags, content, relevance) and pass the explicit fields.
|
|
38
|
+
- **Always write with \`gnosys_add_structured\`** — you are an LLM, so structure the memory yourself (title, category, tags, content, relevance) and pass the explicit fields. NEVER call the freeform \`gnosys_add\`: the server **rejects it with an error** for MCP callers (it would run a second, redundant LLM call and depends on a provider key that can fail silently). \`gnosys_add_structured\` makes no server-side model call. (Freeform \`gnosys_add\` exists only for non-agent scripts/cron, which must set GNOSYS_ALLOW_FREEFORM_ADD=1.)
|
|
39
39
|
- When user says "remember", "memorize", "save this", "note this down", "don't forget" — call \`gnosys_add_structured\`
|
|
40
40
|
- When user states a decision or preference (even casually) — commit to \`decisions\` category
|
|
41
41
|
- When user provides a spec or plan — commit BEFORE starting work
|
|
@@ -48,7 +48,7 @@ This project uses **Gnosys** for persistent memory via MCP. Gnosys uses a centra
|
|
|
48
48
|
|--------|------|
|
|
49
49
|
| Find memories | \`gnosys_discover\` (metadata) → \`gnosys_read\` (content) |
|
|
50
50
|
| Search | \`gnosys_hybrid_search\` (best), \`gnosys_federated_search\` (cross-project), \`gnosys_search\` (keyword), \`gnosys_ask\` (Q&A) |
|
|
51
|
-
| Write | \`gnosys_add_structured\` (explicit fields — **always use this as an agent**; no server-side LLM call). \`gnosys_add\` (freeform) is for non-agent
|
|
51
|
+
| Write | \`gnosys_add_structured\` (explicit fields — **always use this as an agent**; no server-side LLM call). \`gnosys_add\` (freeform) is rejected for MCP callers — non-agent scripts only. |
|
|
52
52
|
| Update | \`gnosys_update\`, \`gnosys_reinforce\` (useful/not_relevant/outdated) |
|
|
53
53
|
| Browse | \`gnosys_list\`, \`gnosys_lens\` (filtered), \`gnosys_tags\`, \`gnosys_graph\` |
|
|
54
54
|
| Maintain | \`gnosys_maintain\`, \`gnosys_stale\`, \`gnosys_history\`, \`gnosys_dashboard\` |
|
|
@@ -22,7 +22,11 @@ export async function runSetupSyncProjectsCommand(opts) {
|
|
|
22
22
|
console.log("");
|
|
23
23
|
// 1. Read registered projects from file registry AND central DB
|
|
24
24
|
const home = process.env.HOME || process.env.USERPROFILE || "/tmp";
|
|
25
|
-
const
|
|
25
|
+
const { getProjectRegistryPath, isTempProjectPath } = await import("./paths.js");
|
|
26
|
+
// Registry lives at ~/.config/gnosys/projects.json (or under
|
|
27
|
+
// GNOSYS_HOME/GNOSYS_CONFIG_DIR when isolated) — always resolved via
|
|
28
|
+
// the shared helper, never hand-rolled from HOME.
|
|
29
|
+
const registryPath = getProjectRegistryPath();
|
|
26
30
|
let fileProjects = [];
|
|
27
31
|
try {
|
|
28
32
|
fileProjects = JSON.parse(await fs.readFile(registryPath, "utf-8"));
|
|
@@ -39,24 +43,46 @@ export async function runSetupSyncProjectsCommand(opts) {
|
|
|
39
43
|
const centralDb = GnosysDB.openCentral();
|
|
40
44
|
if (centralDb.isAvailable()) {
|
|
41
45
|
const allProjects = centralDb.getAllProjects();
|
|
42
|
-
|
|
43
|
-
|
|
46
|
+
// Purge temp-dir ghosts (leaked by test runs) from the central DB
|
|
47
|
+
// so they stop resurfacing in every future sync.
|
|
48
|
+
for (const p of allProjects) {
|
|
49
|
+
if (isTempProjectPath(p.working_directory)) {
|
|
50
|
+
try {
|
|
51
|
+
centralDb.deleteProject(p.id);
|
|
52
|
+
}
|
|
53
|
+
catch {
|
|
54
|
+
// non-critical
|
|
55
|
+
}
|
|
56
|
+
continue;
|
|
57
|
+
}
|
|
58
|
+
dbProjects.push(p.working_directory);
|
|
44
59
|
titleByDir.set(p.working_directory, p.name);
|
|
60
|
+
}
|
|
45
61
|
centralDb.close();
|
|
46
62
|
}
|
|
47
63
|
}
|
|
48
64
|
catch {
|
|
49
65
|
// non-critical
|
|
50
66
|
}
|
|
51
|
-
// Merge: deduplicate by resolved path
|
|
67
|
+
// Merge: deduplicate by resolved path. Temp-dir entries (/tmp,
|
|
68
|
+
// /var/folders — leaked by old versions' test runs) are dropped here,
|
|
69
|
+
// BEFORE the count and the registry write-back. Previously they were
|
|
70
|
+
// only skipped inside the upgrade loop, so "syncing N registered
|
|
71
|
+
// projects" reported hundreds of ghosts and the write-back on line
|
|
72
|
+
// below re-persisted them forever.
|
|
52
73
|
const seen = new Set();
|
|
53
74
|
const projects = [];
|
|
75
|
+
let droppedTemp = 0;
|
|
54
76
|
for (const p of [...fileProjects, ...dbProjects]) {
|
|
55
77
|
const resolved = path.resolve(p);
|
|
56
|
-
if (
|
|
57
|
-
|
|
58
|
-
|
|
78
|
+
if (seen.has(resolved))
|
|
79
|
+
continue;
|
|
80
|
+
seen.add(resolved);
|
|
81
|
+
if (isTempProjectPath(resolved)) {
|
|
82
|
+
droppedTemp++;
|
|
83
|
+
continue;
|
|
59
84
|
}
|
|
85
|
+
projects.push(resolved);
|
|
60
86
|
}
|
|
61
87
|
if (projects.length === 0) {
|
|
62
88
|
console.log(" no registered projects found");
|
|
@@ -79,10 +105,7 @@ export async function runSetupSyncProjectsCommand(opts) {
|
|
|
79
105
|
const skipped = [];
|
|
80
106
|
const failed = [];
|
|
81
107
|
for (const projectDir of projects) {
|
|
82
|
-
//
|
|
83
|
-
if (projectDir.startsWith("/tmp/") || projectDir.startsWith("/private/tmp/") || projectDir.startsWith("/var/folders/") || projectDir.startsWith("/private/var/folders/")) {
|
|
84
|
-
continue;
|
|
85
|
-
}
|
|
108
|
+
// Temp/test directories were already filtered out of `projects`.
|
|
86
109
|
const storePath = path.join(projectDir, ".gnosys");
|
|
87
110
|
try {
|
|
88
111
|
await fs.stat(storePath);
|
|
@@ -131,7 +154,8 @@ export async function runSetupSyncProjectsCommand(opts) {
|
|
|
131
154
|
}
|
|
132
155
|
// Stop the lead-in spinner now that the iteration is done. Resolved
|
|
133
156
|
// before any per-section output so the cursor is on a fresh line.
|
|
134
|
-
syncSpinner.ok(`synced ${projects.length} registered projects`, `${upgraded.length} upgraded · ${skipped.length} skipped · ${failed.length} failed`
|
|
157
|
+
syncSpinner.ok(`synced ${projects.length} registered projects`, `${upgraded.length} upgraded · ${skipped.length} skipped · ${failed.length} failed` +
|
|
158
|
+
(droppedTemp > 0 ? ` · ${droppedTemp} temp entries pruned` : ""));
|
|
135
159
|
// 3. Update global agent rules
|
|
136
160
|
try {
|
|
137
161
|
let centralDb = null;
|
package/dist/lib/syncCommand.js
CHANGED
|
@@ -30,7 +30,14 @@ export async function runSyncCommand(opts) {
|
|
|
30
30
|
process.exitCode = 1;
|
|
31
31
|
return;
|
|
32
32
|
}
|
|
33
|
-
|
|
33
|
+
// Default to "all": refresh every IDE rules file actually present in the
|
|
34
|
+
// project (detectAllTargets only touches existing files, never creating
|
|
35
|
+
// configs for unused IDEs). A project accumulates multiple IDEs over its
|
|
36
|
+
// life — bare `gnosys sync` must keep all of them current, not just the one
|
|
37
|
+
// recorded in agentRulesTarget at first setup. This matches the upgrade-time
|
|
38
|
+
// "sync registered projects" path, which already uses "all". Use
|
|
39
|
+
// `--target <ide>` to refresh a single file.
|
|
40
|
+
const resolvedTarget = target || "all";
|
|
34
41
|
const results = await syncToTarget(centralDb, projectDir, resolvedTarget, identity.projectId);
|
|
35
42
|
if (results.length === 0) {
|
|
36
43
|
console.error("No targets found. Create a CLAUDE.md, .cursor/, or .codex/ directory first.");
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "gnosys",
|
|
3
|
-
"version": "5.
|
|
3
|
+
"version": "5.16.0",
|
|
4
4
|
"description": "Gnosys — Persistent Memory for AI Agents. Sandbox-first runtime, central SQLite brain, federated search, Dream Mode, Web Knowledge Base, Obsidian export.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "dist/index.js",
|