@ijfw/memory-server 1.6.2 → 1.6.3
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/package.json +1 -1
- package/src/brain/dream-pipeline.js +17 -1
- package/src/brain/seed-gate.js +108 -0
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@ijfw/memory-server",
|
|
3
|
-
"version": "1.6.
|
|
3
|
+
"version": "1.6.3",
|
|
4
4
|
"description": "Cross-platform persistent memory server for IJFW. 14 MCP tools (memory + admin/update + brain). Works with 15 platforms: 14 via MCP (Claude Code, Codex, Gemini CLI, Cursor, Windsurf, Copilot, Hermes, Wayland, OpenCode, QwenCode, Cline, KimiCode, OpenClaw, Antigravity) plus Aider via the rules-only tier.",
|
|
5
5
|
"author": "Sean Donahoe",
|
|
6
6
|
"contributors": [
|
|
@@ -21,6 +21,7 @@ import {
|
|
|
21
21
|
} from 'node:fs';
|
|
22
22
|
import { join, dirname } from 'node:path';
|
|
23
23
|
import { resolveBrainPaths } from './paths.js';
|
|
24
|
+
import { shouldSeedProject } from './seed-gate.js';
|
|
24
25
|
import { scanInbox, writeManifest, commitProcessed, isProcessed } from './dump-ingest.js';
|
|
25
26
|
import { extractFile } from './extractors/index.js';
|
|
26
27
|
import { BudgetGuard } from './budget-guard.js';
|
|
@@ -217,9 +218,24 @@ function isProcessedDouble(db, processedDir, fileName) {
|
|
|
217
218
|
export async function runDreamCycle({ db, repoRoot, env = process.env, cycleId, extractFacts } = {}) {
|
|
218
219
|
if (!db) throw new Error('dream-pipeline: db required');
|
|
219
220
|
if (!repoRoot) throw new Error('dream-pipeline: repoRoot required');
|
|
221
|
+
// Seed gate: the dream cycle materializes the VISIBLE `ijfw/` layer
|
|
222
|
+
// (dump/inbox, dump/processed, wiki/...). Do not create it in a directory
|
|
223
|
+
// that is not a real project -- a throwaway scratch dir or an ephemeral
|
|
224
|
+
// "temporary space" (Wayland) running a one-shot chat should stay clean.
|
|
225
|
+
// A project marker (.git, a manifest) or an explicit `ijfw init` re-enables
|
|
226
|
+
// it. Memory recall still works in-session; only the on-disk content layer
|
|
227
|
+
// is withheld. Honest no-op return so callers/receipts see why nothing ran.
|
|
228
|
+
const cid0 = cycleId || `cycle-${Date.now()}`;
|
|
229
|
+
if (!shouldSeedProject(repoRoot)) {
|
|
230
|
+
return {
|
|
231
|
+
processed: 0, pagesCompiled: 0, factsInserted: 0,
|
|
232
|
+
budgetExhausted: false, cycleId: cid0, errors: [],
|
|
233
|
+
skipped: 'no-project-marker',
|
|
234
|
+
};
|
|
235
|
+
}
|
|
220
236
|
ensureFactsTable(db);
|
|
221
237
|
const paths = resolveBrainPaths(repoRoot);
|
|
222
|
-
const cid =
|
|
238
|
+
const cid = cid0;
|
|
223
239
|
// Parse budget caps from env explicitly so zero is respected (Number('0')||default
|
|
224
240
|
// would silently fall back to the default; we need the caller's $0 to mean $0).
|
|
225
241
|
const cycleUsdRaw = env.IJFW_DREAM_BUDGET_USD != null ? Number(env.IJFW_DREAM_BUDGET_USD) : undefined;
|
|
@@ -0,0 +1,108 @@
|
|
|
1
|
+
// IJFW seed gate -- "should we materialize on-disk content in this directory?"
|
|
2
|
+
//
|
|
3
|
+
// Single rule, shared across the whole product: IJFW only writes project
|
|
4
|
+
// artifacts (the visible `ijfw/` brain layer, AGENTS.md, CLAUDE.md, the
|
|
5
|
+
// codebase index, the `.ijfw/project.type` cold scan) into a directory that is
|
|
6
|
+
// actually a project. "A project" means it carries a recognized marker (a VCS
|
|
7
|
+
// dir, a language manifest) OR the operator explicitly blessed it with
|
|
8
|
+
// `ijfw init` (which drops `.ijfw/project`).
|
|
9
|
+
//
|
|
10
|
+
// Why this exists: session-start hooks fire on EVERY new chat, including
|
|
11
|
+
// throwaway scratch dirs and ephemeral "temporary spaces" (e.g. Wayland). The
|
|
12
|
+
// old behavior seeded those unconditionally, so a one-shot `print(7*6)` chat
|
|
13
|
+
// littered `ijfw/`, AGENTS.md, and CLAUDE.md into a dir the user never meant to
|
|
14
|
+
// keep. Memory recall still works in-session for those dirs -- we just don't
|
|
15
|
+
// write anything to disk until the dir proves it's a real project.
|
|
16
|
+
//
|
|
17
|
+
// This is the JS mirror of the bash `ijfw_should_seed` (seed-gate.sh) and the
|
|
18
|
+
// indexer's `ijfw_has_project_marker` (scripts/build-codebase-index.sh). The
|
|
19
|
+
// three marker lists are kept identical by a drift test
|
|
20
|
+
// (test/brain/test-seed-gate-drift.js). Edit all three together or the test
|
|
21
|
+
// fails.
|
|
22
|
+
|
|
23
|
+
import { existsSync, realpathSync } from 'node:fs';
|
|
24
|
+
import { join, dirname, parse as parsePath, sep } from 'node:path';
|
|
25
|
+
import { homedir } from 'node:os';
|
|
26
|
+
|
|
27
|
+
// Canonical project-marker list. MUST stay byte-identical (same set) to the
|
|
28
|
+
// bash list in seed-gate.sh and the indexer's ijfw_has_project_marker. The
|
|
29
|
+
// `.ijfw/project` entry is the `ijfw init` override.
|
|
30
|
+
export const PROJECT_MARKERS = Object.freeze([
|
|
31
|
+
'.git',
|
|
32
|
+
'package.json',
|
|
33
|
+
'go.mod',
|
|
34
|
+
'Cargo.toml',
|
|
35
|
+
'pyproject.toml',
|
|
36
|
+
'setup.py',
|
|
37
|
+
'tsconfig.json',
|
|
38
|
+
'pom.xml',
|
|
39
|
+
'build.gradle',
|
|
40
|
+
'build.gradle.kts',
|
|
41
|
+
'Gemfile',
|
|
42
|
+
'composer.json',
|
|
43
|
+
'deno.json',
|
|
44
|
+
'deno.jsonc',
|
|
45
|
+
'mix.exs',
|
|
46
|
+
'Package.swift',
|
|
47
|
+
'requirements.txt',
|
|
48
|
+
'.hg',
|
|
49
|
+
'.svn',
|
|
50
|
+
'.ijfw/project',
|
|
51
|
+
]);
|
|
52
|
+
|
|
53
|
+
// True when `dir` carries any recognized project marker. Pure existence checks;
|
|
54
|
+
// never throws.
|
|
55
|
+
export function hasProjectMarker(dir) {
|
|
56
|
+
if (!dir || typeof dir !== 'string') return false;
|
|
57
|
+
for (const m of PROJECT_MARKERS) {
|
|
58
|
+
try {
|
|
59
|
+
// `.ijfw/project` is a nested path; join handles both flat and nested.
|
|
60
|
+
if (existsSync(join(dir, ...m.split('/')))) return true;
|
|
61
|
+
} catch {
|
|
62
|
+
// Unreadable candidate -- treat as absent, keep scanning.
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
return false;
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
// Resolve the physical path of a directory, falling back to the input on error
|
|
69
|
+
// so callers always get a usable string.
|
|
70
|
+
function physOf(dir) {
|
|
71
|
+
try {
|
|
72
|
+
return realpathSync(dir);
|
|
73
|
+
} catch {
|
|
74
|
+
return dir;
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
// The full seed decision: refuse the filesystem root, the home directory, and
|
|
79
|
+
// any ancestor of home (the issue #16 privacy hole -- seeding /Users or /home
|
|
80
|
+
// would walk every user's home), THEN require a project marker.
|
|
81
|
+
//
|
|
82
|
+
// Returns true only when `dir` is a real, safe project directory to write into.
|
|
83
|
+
export function shouldSeedProject(dir) {
|
|
84
|
+
if (!dir || typeof dir !== 'string') return false;
|
|
85
|
+
const phys = physOf(dir);
|
|
86
|
+
if (!phys || phys === sep) return false;
|
|
87
|
+
|
|
88
|
+
// A filesystem/drive root is its own parent (covers POSIX '/' and Windows
|
|
89
|
+
// drive roots like 'C:\\').
|
|
90
|
+
const root = parsePath(phys).root;
|
|
91
|
+
if (phys === root) return false;
|
|
92
|
+
if (dirname(phys) === phys) return false;
|
|
93
|
+
|
|
94
|
+
// Refuse the home directory and any ancestor of it. Fail closed when home is
|
|
95
|
+
// unresolvable -- we cannot prove the target isn't home, so do not seed.
|
|
96
|
+
let homePhys;
|
|
97
|
+
try {
|
|
98
|
+
homePhys = realpathSync(homedir());
|
|
99
|
+
} catch {
|
|
100
|
+
homePhys = homedir();
|
|
101
|
+
}
|
|
102
|
+
if (!homePhys) return false;
|
|
103
|
+
if (phys === homePhys) return false;
|
|
104
|
+
// phys is an ancestor of home when home lives under phys/.
|
|
105
|
+
if ((homePhys + sep).startsWith(phys + sep)) return false;
|
|
106
|
+
|
|
107
|
+
return hasProjectMarker(phys);
|
|
108
|
+
}
|