memorix 1.6.0 → 1.6.1
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/CHANGELOG.md +24 -0
- package/TEAM.md +86 -86
- package/dist/cli/index.js +317 -62
- package/dist/cli/memcode.js +12 -1
- package/dist/index.js +56 -9
- package/dist/maintenance-runner.js +54 -7
- package/dist/media-audio-runner.js +54 -7
- package/dist/media-video-runner.js +54 -7
- package/dist/memcode-runtime/CHANGELOG.md +24 -0
- package/dist/sdk.js +55 -8
- package/dist/vector-backfill-runner.js +54 -7
- package/docs/AGENT_OPERATOR_PLAYBOOK.md +11 -3
- package/docs/API_REFERENCE.md +1 -1
- package/docs/DESIGN_DECISIONS.md +357 -357
- package/docs/DOCKER.md +5 -1
- package/docs/PERFORMANCE.md +2 -2
- package/docs/README.md +3 -1
- package/docs/SETUP.md +8 -2
- package/package.json +1 -1
- package/plugins/claude/memorix/.claude-plugin/plugin.json +1 -1
- package/plugins/codebuddy/memorix/.codebuddy-plugin/plugin.json +1 -1
- package/plugins/codex/memorix/.codex-plugin/plugin.json +1 -1
- package/plugins/copilot/memorix/plugin.json +1 -1
- package/plugins/gemini/memorix/gemini-extension.json +1 -1
- package/plugins/omp/memorix/package.json +1 -1
- package/plugins/openclaw/memorix/.codex-plugin/plugin.json +1 -1
- package/plugins/pi/memorix/package.json +1 -1
- package/server.json +2 -2
- package/src/audit/index.ts +156 -156
- package/src/cli/commands/agent-integrations.ts +9 -3
- package/src/cli/commands/doctor.ts +46 -15
- package/src/cli/commands/formation.ts +48 -48
- package/src/cli/commands/git-hook-install.ts +111 -111
- package/src/cli/commands/handoff.ts +54 -54
- package/src/cli/commands/hook.ts +13 -1
- package/src/cli/commands/hooks-status.ts +63 -63
- package/src/cli/commands/ingest.ts +44 -44
- package/src/cli/commands/integrate-shared.ts +15 -15
- package/src/cli/commands/lock.ts +82 -82
- package/src/cli/commands/message.ts +104 -104
- package/src/cli/commands/poll.ts +58 -58
- package/src/cli/commands/reasoning.ts +118 -118
- package/src/cli/commands/serve-http.ts +68 -7
- package/src/cli/commands/skills.ts +114 -114
- package/src/cli/commands/task.ts +167 -167
- package/src/cli/commands/transfer.ts +47 -47
- package/src/cli/heap.ts +76 -0
- package/src/cli/index.ts +406 -406
- package/src/cli/invocation.ts +7 -4
- package/src/cli/tui/ChatView.tsx +234 -234
- package/src/cli/tui/CommandBar.tsx +312 -312
- package/src/cli/tui/ContextRail.tsx +118 -118
- package/src/cli/tui/HeaderBar.tsx +72 -72
- package/src/cli/tui/LogoBanner.tsx +51 -51
- package/src/cli/tui/index.ts +41 -41
- package/src/cli/version.ts +7 -7
- package/src/cli/workbench.ts +1 -1
- package/src/dashboard/project-classification.ts +64 -64
- package/src/embedding/provider.ts +76 -6
- package/src/git/hooks-path.ts +85 -85
- package/src/hooks/handler.ts +9 -18
- package/src/hooks/lifecycle.ts +173 -0
- package/src/hooks/pattern-detector.ts +173 -173
- package/src/hooks/significance-filter.ts +250 -250
- package/src/llm/memory-manager.ts +328 -328
- package/src/llm/quality.ts +248 -248
- package/src/memory/attribution-guard.ts +249 -249
- package/src/memory/disclosure-policy.ts +135 -135
- package/src/memory/entity-extractor.ts +197 -197
- package/src/memory/formation/evaluate.ts +217 -217
- package/src/memory/freshness.ts +122 -122
- package/src/memory/graph.ts +197 -197
- package/src/memory/refs.ts +94 -94
- package/src/memory/secret-filter.ts +79 -79
- package/src/orchestrate/adapters/claude-stream.ts +192 -192
- package/src/orchestrate/adapters/claude.ts +111 -111
- package/src/orchestrate/adapters/codex-stream.ts +134 -134
- package/src/orchestrate/adapters/codex.ts +41 -41
- package/src/orchestrate/adapters/gemini-stream.ts +166 -166
- package/src/orchestrate/adapters/gemini.ts +42 -42
- package/src/orchestrate/adapters/index.ts +73 -73
- package/src/orchestrate/adapters/opencode-stream.ts +143 -143
- package/src/orchestrate/adapters/opencode.ts +47 -47
- package/src/orchestrate/adapters/types.ts +77 -77
- package/src/orchestrate/capability-router.ts +284 -284
- package/src/orchestrate/cost-tracker.ts +219 -219
- package/src/orchestrate/error-recovery.ts +191 -191
- package/src/orchestrate/evidence.ts +140 -140
- package/src/orchestrate/ledger.ts +110 -110
- package/src/orchestrate/memorix-bridge.ts +343 -343
- package/src/orchestrate/output-budget.ts +80 -80
- package/src/orchestrate/permission.ts +152 -152
- package/src/orchestrate/pipeline-trace.ts +131 -131
- package/src/orchestrate/prompt-builder.ts +155 -155
- package/src/orchestrate/ring-buffer.ts +37 -37
- package/src/orchestrate/task-graph.ts +389 -389
- package/src/project/aliases.ts +374 -374
- package/src/rules/adapters/trae.ts +56 -56
- package/src/rules/syncer.ts +235 -235
- package/src/search/query-expansion.ts +52 -52
- package/src/server/formation-timeout.ts +27 -27
- package/src/skills/mini-skills.ts +386 -386
- package/src/store/chat-store.ts +119 -119
- package/src/store/graph-store.ts +249 -249
- package/src/store/mini-skill-store.ts +349 -349
- package/src/store/persistence-json.ts +212 -212
- package/src/store/persistence.ts +291 -291
- package/src/store/project-affinity.ts +195 -195
- package/src/team/event-bus.ts +76 -76
- package/src/team/handoff.ts +161 -161
- package/src/team/poll.ts +132 -132
- package/src/workspace/mcp-adapters/codex.ts +191 -191
- package/src/workspace/mcp-adapters/copilot.ts +105 -105
- package/src/workspace/mcp-adapters/cursor.ts +53 -53
- package/src/workspace/mcp-adapters/kiro.ts +64 -64
- package/src/workspace/mcp-adapters/opencode.ts +123 -123
- package/src/workspace/mcp-adapters/trae.ts +134 -134
- package/src/workspace/mcp-adapters/windsurf.ts +91 -91
- package/src/workspace/sanitizer.ts +60 -60
package/docs/DOCKER.md
CHANGED
|
@@ -128,7 +128,11 @@ docker compose ps
|
|
|
128
128
|
|
|
129
129
|
### HTTP session idle timeout
|
|
130
130
|
|
|
131
|
-
The HTTP MCP transport defaults to a
|
|
131
|
+
The HTTP MCP transport defaults to a 12-hour idle timeout. The example
|
|
132
|
+
`compose.yaml` raises this to 24 hours for IDEs that keep one HTTP MCP session
|
|
133
|
+
open while the user reads diffs, runs tests, or switches focus. When a session
|
|
134
|
+
does expire, the service returns a fast `404` so the client can initialize a
|
|
135
|
+
fresh MCP session.
|
|
132
136
|
|
|
133
137
|
To change it:
|
|
134
138
|
|
package/docs/PERFORMANCE.md
CHANGED
|
@@ -71,7 +71,7 @@ On the release development machine used for this check, the healthy HTTP service
|
|
|
71
71
|
|
|
72
72
|
| Knob | Default | Use When |
|
|
73
73
|
| --- | --- | --- |
|
|
74
|
-
| `MEMORIX_SESSION_TIMEOUT_MS` | `
|
|
74
|
+
| `MEMORIX_SESSION_TIMEOUT_MS` | `43200000` (12 h) | Set a shorter GC window for supervised clients, or `0` to disable idle session GC |
|
|
75
75
|
| `MEMORIX_FORMATION_TIMEOUT_MS` | `12000` (12 s) | Raise when LLM-backed formation should outlive slow proxy/provider hops |
|
|
76
76
|
| `MEMORIX_LLM_API_KEY` / `OPENAI_API_KEY` | unset | Enable LLM-backed enrichment, extraction, rerank, or skill generation |
|
|
77
77
|
| `MEMORIX_LLM_TIMEOUT_MS` | `30000` (30 s) | Bound a single LLM-backed extraction/resolve call |
|
|
@@ -86,7 +86,7 @@ On the release development machine used for this check, the healthy HTTP service
|
|
|
86
86
|
## Operator Guidance
|
|
87
87
|
|
|
88
88
|
- For memory-only use, prefer stdio MCP or a lightweight `memorix_session_start`; do not join orchestration coordination state by default.
|
|
89
|
-
-
|
|
89
|
+
- HTTP sessions now default to 12 hours and return a fast `404` with a reinitialize hint after expiry. Set `MEMORIX_SESSION_TIMEOUT_MS=0` only when the host has its own reliable lifecycle cleanup; otherwise retain a bounded GC window.
|
|
90
90
|
- If LLM-backed formation is timing out against a slow proxy/provider, raise `MEMORIX_FORMATION_TIMEOUT_MS` and keep it higher than `MEMORIX_LLM_TIMEOUT_MS`, because the full pipeline can include multiple LLM-backed stages.
|
|
91
91
|
- For Docker, use it when you want a managed HTTP service. Do not use image size alone as the runtime memory estimate.
|
|
92
92
|
- For orchestrated subagent work, expect CPU and disk activity proportional to the spawned agents and verification commands.
|
package/docs/README.md
CHANGED
|
@@ -117,11 +117,13 @@ Historical/deep-reference documents may describe older designs. If they conflict
|
|
|
117
117
|
|
|
118
118
|
## Current Product Line
|
|
119
119
|
|
|
120
|
-
The released product is on the **1.
|
|
120
|
+
The released product is on the **1.6 line**. The current baseline has:
|
|
121
121
|
|
|
122
122
|
- `memorix setup --agent <agent> --global` is the default agent integration command
|
|
123
123
|
- `memorix serve` is the manual stdio MCP server for external agents
|
|
124
124
|
- `memorix background start` runs the shared HTTP MCP service and dashboard
|
|
125
|
+
- context and CodeGraph commands return bounded task receipts before detailed diagnostics
|
|
126
|
+
- HTTP MCP sessions have a bounded, explicit lifecycle and fail with a reinitialize hint after expiry
|
|
125
127
|
- `memorix integrate --agent <agent>` and `memorix hooks install --agent <agent>` remain manual/fallback generation commands
|
|
126
128
|
- `memorix` / `memcode` open memcode, the bundled terminal agent that uses the same Memorix memory pool
|
|
127
129
|
- `~/.memorix/config.toml` and project `memorix.toml` are the user-facing configuration model
|
package/docs/SETUP.md
CHANGED
|
@@ -42,7 +42,7 @@ Memorix project identity comes from Git. Open or initialize a real Git repositor
|
|
|
42
42
|
git init
|
|
43
43
|
```
|
|
44
44
|
|
|
45
|
-
`projectRoot` and current working directory are detection anchors. The final project identity is derived from the real Git root and remote metadata.
|
|
45
|
+
`projectRoot` and current working directory are detection anchors. The final project identity is derived from the real Git root and remote metadata. Memorix resolves an existing project directory to its real filesystem path before binding it, so equivalent macOS paths such as `/var/...` and `/private/var/...` do not create separate project or Claude local-MCP entries.
|
|
46
46
|
|
|
47
47
|
---
|
|
48
48
|
|
|
@@ -247,13 +247,19 @@ Generic HTTP MCP config:
|
|
|
247
247
|
|
|
248
248
|
For multi-project HTTP usage, agents should call `memorix_session_start(projectRoot=...)` with the absolute workspace path when available. This prevents cross-project drift when the background service is shared.
|
|
249
249
|
|
|
250
|
-
HTTP sessions idle out after
|
|
250
|
+
HTTP sessions idle out after 12 hours by default. An expired session receives a
|
|
251
|
+
fast `404` that tells the client to initialize again, rather than being treated
|
|
252
|
+
as a project-binding failure. For clients that intentionally need a longer
|
|
253
|
+
window:
|
|
251
254
|
|
|
252
255
|
```powershell
|
|
253
256
|
$env:MEMORIX_SESSION_TIMEOUT_MS = "86400000"
|
|
254
257
|
memorix background restart
|
|
255
258
|
```
|
|
256
259
|
|
|
260
|
+
Set `MEMORIX_SESSION_TIMEOUT_MS=0` only when the host has its own reliable
|
|
261
|
+
session cleanup; otherwise keep a bounded idle window.
|
|
262
|
+
|
|
257
263
|
### Option E: Docker HTTP service
|
|
258
264
|
|
|
259
265
|
```bash
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "memorix",
|
|
3
3
|
"mcpName": "io.github.AVIDS2/memorix",
|
|
4
|
-
"version": "1.6.
|
|
4
|
+
"version": "1.6.1",
|
|
5
5
|
"description": "Local-first shared memory layer for AI coding agents across MCP clients, Git history, reasoning context, and project sessions.",
|
|
6
6
|
"type": "module",
|
|
7
7
|
"sideEffects": false,
|
package/server.json
CHANGED
|
@@ -7,7 +7,7 @@
|
|
|
7
7
|
"url": "https://github.com/AVIDS2/memorix",
|
|
8
8
|
"source": "github"
|
|
9
9
|
},
|
|
10
|
-
"version": "1.6.
|
|
10
|
+
"version": "1.6.1",
|
|
11
11
|
"websiteUrl": "https://github.com/AVIDS2/memorix#readme",
|
|
12
12
|
"icons": [
|
|
13
13
|
{
|
|
@@ -22,7 +22,7 @@
|
|
|
22
22
|
{
|
|
23
23
|
"registryType": "npm",
|
|
24
24
|
"identifier": "memorix",
|
|
25
|
-
"version": "1.6.
|
|
25
|
+
"version": "1.6.1",
|
|
26
26
|
"runtimeHint": "npx",
|
|
27
27
|
"packageArguments": [
|
|
28
28
|
{
|
package/src/audit/index.ts
CHANGED
|
@@ -1,156 +1,156 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Audit Module - Track Memorix-written files
|
|
3
|
-
*
|
|
4
|
-
* Records all files written by Memorix to distinguish them from
|
|
5
|
-
* project-native files. Provides audit trail for cleanup and rollback.
|
|
6
|
-
*/
|
|
7
|
-
|
|
8
|
-
import * as fs from 'node:fs/promises';
|
|
9
|
-
import * as path from 'node:path';
|
|
10
|
-
import { homedir } from 'node:os';
|
|
11
|
-
|
|
12
|
-
function getAuditFilePath(): string {
|
|
13
|
-
return process.env.MEMORIX_AUDIT_FILE || path.join(homedir(), '.memorix', 'audit.json');
|
|
14
|
-
}
|
|
15
|
-
|
|
16
|
-
export interface AuditEntry {
|
|
17
|
-
type: 'hook' | 'rule' | 'other';
|
|
18
|
-
agent?: string;
|
|
19
|
-
path: string;
|
|
20
|
-
createdAt: string;
|
|
21
|
-
}
|
|
22
|
-
|
|
23
|
-
export interface ProjectAudit {
|
|
24
|
-
projectRoot: string;
|
|
25
|
-
installedAt: string;
|
|
26
|
-
entries: AuditEntry[];
|
|
27
|
-
}
|
|
28
|
-
|
|
29
|
-
export interface AuditData {
|
|
30
|
-
version: string;
|
|
31
|
-
projects: Record<string, ProjectAudit>;
|
|
32
|
-
}
|
|
33
|
-
|
|
34
|
-
/**
|
|
35
|
-
* Load audit data from disk.
|
|
36
|
-
*/
|
|
37
|
-
export async function loadAudit(): Promise<AuditData> {
|
|
38
|
-
try {
|
|
39
|
-
const content = await fs.readFile(getAuditFilePath(), 'utf-8');
|
|
40
|
-
return JSON.parse(content) as AuditData;
|
|
41
|
-
} catch {
|
|
42
|
-
// No audit file yet
|
|
43
|
-
return {
|
|
44
|
-
version: '1.0.0',
|
|
45
|
-
projects: {},
|
|
46
|
-
};
|
|
47
|
-
}
|
|
48
|
-
}
|
|
49
|
-
|
|
50
|
-
/**
|
|
51
|
-
* Save audit data to disk.
|
|
52
|
-
*/
|
|
53
|
-
export async function saveAudit(data: AuditData): Promise<void> {
|
|
54
|
-
const auditFile = getAuditFilePath();
|
|
55
|
-
const dir = path.dirname(auditFile);
|
|
56
|
-
await fs.mkdir(dir, { recursive: true });
|
|
57
|
-
await fs.writeFile(auditFile, JSON.stringify(data, null, 2), 'utf-8');
|
|
58
|
-
}
|
|
59
|
-
|
|
60
|
-
/**
|
|
61
|
-
* Get project ID from project root via .git detection.
|
|
62
|
-
*/
|
|
63
|
-
export function getProjectId(projectRoot: string): string {
|
|
64
|
-
try {
|
|
65
|
-
const { detectProject } = require('../project/detector.js');
|
|
66
|
-
const project = detectProject(projectRoot);
|
|
67
|
-
if (project) return project.id;
|
|
68
|
-
} catch { /* fallback below */ }
|
|
69
|
-
const normalized = projectRoot.replace(/\\/g, '/');
|
|
70
|
-
return `untracked/${normalized}`;
|
|
71
|
-
}
|
|
72
|
-
|
|
73
|
-
/**
|
|
74
|
-
* Record a file written by Memorix.
|
|
75
|
-
*/
|
|
76
|
-
export async function recordFile(
|
|
77
|
-
projectRoot: string,
|
|
78
|
-
type: AuditEntry['type'],
|
|
79
|
-
filePath: string,
|
|
80
|
-
agent?: string,
|
|
81
|
-
): Promise<void> {
|
|
82
|
-
const data = await loadAudit();
|
|
83
|
-
const projectId = getProjectId(projectRoot);
|
|
84
|
-
|
|
85
|
-
if (!data.projects[projectId]) {
|
|
86
|
-
data.projects[projectId] = {
|
|
87
|
-
projectRoot,
|
|
88
|
-
installedAt: new Date().toISOString(),
|
|
89
|
-
entries: [],
|
|
90
|
-
};
|
|
91
|
-
}
|
|
92
|
-
|
|
93
|
-
// Check if entry already exists
|
|
94
|
-
const existingIndex = data.projects[projectId].entries.findIndex(
|
|
95
|
-
(e) => e.path === filePath
|
|
96
|
-
);
|
|
97
|
-
|
|
98
|
-
if (existingIndex === -1) {
|
|
99
|
-
data.projects[projectId].entries.push({
|
|
100
|
-
type,
|
|
101
|
-
agent,
|
|
102
|
-
path: filePath,
|
|
103
|
-
createdAt: new Date().toISOString(),
|
|
104
|
-
});
|
|
105
|
-
}
|
|
106
|
-
|
|
107
|
-
await saveAudit(data);
|
|
108
|
-
}
|
|
109
|
-
|
|
110
|
-
/**
|
|
111
|
-
* Get all files written by Memorix for a project.
|
|
112
|
-
*/
|
|
113
|
-
export async function getProjectFiles(projectRoot: string): Promise<AuditEntry[]> {
|
|
114
|
-
const data = await loadAudit();
|
|
115
|
-
const projectId = getProjectId(projectRoot);
|
|
116
|
-
return data.projects[projectId]?.entries || [];
|
|
117
|
-
}
|
|
118
|
-
|
|
119
|
-
/**
|
|
120
|
-
* Remove a file from audit (when uninstalled).
|
|
121
|
-
*/
|
|
122
|
-
export async function removeFile(projectRoot: string, filePath: string): Promise<void> {
|
|
123
|
-
const data = await loadAudit();
|
|
124
|
-
const projectId = getProjectId(projectRoot);
|
|
125
|
-
|
|
126
|
-
if (!data.projects[projectId]) return;
|
|
127
|
-
|
|
128
|
-
data.projects[projectId].entries = data.projects[projectId].entries.filter(
|
|
129
|
-
(e) => e.path !== filePath
|
|
130
|
-
);
|
|
131
|
-
|
|
132
|
-
// If no entries left, remove the project
|
|
133
|
-
if (data.projects[projectId].entries.length === 0) {
|
|
134
|
-
delete data.projects[projectId];
|
|
135
|
-
}
|
|
136
|
-
|
|
137
|
-
await saveAudit(data);
|
|
138
|
-
}
|
|
139
|
-
|
|
140
|
-
/**
|
|
141
|
-
* Get all audit entries across all projects.
|
|
142
|
-
*/
|
|
143
|
-
export async function getAllAuditEntries(): Promise<
|
|
144
|
-
Array<{ projectId: string; entry: AuditEntry }>
|
|
145
|
-
> {
|
|
146
|
-
const data = await loadAudit();
|
|
147
|
-
const entries: Array<{ projectId: string; entry: AuditEntry }> = [];
|
|
148
|
-
|
|
149
|
-
for (const [projectId, project] of Object.entries(data.projects)) {
|
|
150
|
-
for (const entry of project.entries) {
|
|
151
|
-
entries.push({ projectId, entry });
|
|
152
|
-
}
|
|
153
|
-
}
|
|
154
|
-
|
|
155
|
-
return entries;
|
|
156
|
-
}
|
|
1
|
+
/**
|
|
2
|
+
* Audit Module - Track Memorix-written files
|
|
3
|
+
*
|
|
4
|
+
* Records all files written by Memorix to distinguish them from
|
|
5
|
+
* project-native files. Provides audit trail for cleanup and rollback.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import * as fs from 'node:fs/promises';
|
|
9
|
+
import * as path from 'node:path';
|
|
10
|
+
import { homedir } from 'node:os';
|
|
11
|
+
|
|
12
|
+
function getAuditFilePath(): string {
|
|
13
|
+
return process.env.MEMORIX_AUDIT_FILE || path.join(homedir(), '.memorix', 'audit.json');
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
export interface AuditEntry {
|
|
17
|
+
type: 'hook' | 'rule' | 'other';
|
|
18
|
+
agent?: string;
|
|
19
|
+
path: string;
|
|
20
|
+
createdAt: string;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
export interface ProjectAudit {
|
|
24
|
+
projectRoot: string;
|
|
25
|
+
installedAt: string;
|
|
26
|
+
entries: AuditEntry[];
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
export interface AuditData {
|
|
30
|
+
version: string;
|
|
31
|
+
projects: Record<string, ProjectAudit>;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
/**
|
|
35
|
+
* Load audit data from disk.
|
|
36
|
+
*/
|
|
37
|
+
export async function loadAudit(): Promise<AuditData> {
|
|
38
|
+
try {
|
|
39
|
+
const content = await fs.readFile(getAuditFilePath(), 'utf-8');
|
|
40
|
+
return JSON.parse(content) as AuditData;
|
|
41
|
+
} catch {
|
|
42
|
+
// No audit file yet
|
|
43
|
+
return {
|
|
44
|
+
version: '1.0.0',
|
|
45
|
+
projects: {},
|
|
46
|
+
};
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
/**
|
|
51
|
+
* Save audit data to disk.
|
|
52
|
+
*/
|
|
53
|
+
export async function saveAudit(data: AuditData): Promise<void> {
|
|
54
|
+
const auditFile = getAuditFilePath();
|
|
55
|
+
const dir = path.dirname(auditFile);
|
|
56
|
+
await fs.mkdir(dir, { recursive: true });
|
|
57
|
+
await fs.writeFile(auditFile, JSON.stringify(data, null, 2), 'utf-8');
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
/**
|
|
61
|
+
* Get project ID from project root via .git detection.
|
|
62
|
+
*/
|
|
63
|
+
export function getProjectId(projectRoot: string): string {
|
|
64
|
+
try {
|
|
65
|
+
const { detectProject } = require('../project/detector.js');
|
|
66
|
+
const project = detectProject(projectRoot);
|
|
67
|
+
if (project) return project.id;
|
|
68
|
+
} catch { /* fallback below */ }
|
|
69
|
+
const normalized = projectRoot.replace(/\\/g, '/');
|
|
70
|
+
return `untracked/${normalized}`;
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
/**
|
|
74
|
+
* Record a file written by Memorix.
|
|
75
|
+
*/
|
|
76
|
+
export async function recordFile(
|
|
77
|
+
projectRoot: string,
|
|
78
|
+
type: AuditEntry['type'],
|
|
79
|
+
filePath: string,
|
|
80
|
+
agent?: string,
|
|
81
|
+
): Promise<void> {
|
|
82
|
+
const data = await loadAudit();
|
|
83
|
+
const projectId = getProjectId(projectRoot);
|
|
84
|
+
|
|
85
|
+
if (!data.projects[projectId]) {
|
|
86
|
+
data.projects[projectId] = {
|
|
87
|
+
projectRoot,
|
|
88
|
+
installedAt: new Date().toISOString(),
|
|
89
|
+
entries: [],
|
|
90
|
+
};
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
// Check if entry already exists
|
|
94
|
+
const existingIndex = data.projects[projectId].entries.findIndex(
|
|
95
|
+
(e) => e.path === filePath
|
|
96
|
+
);
|
|
97
|
+
|
|
98
|
+
if (existingIndex === -1) {
|
|
99
|
+
data.projects[projectId].entries.push({
|
|
100
|
+
type,
|
|
101
|
+
agent,
|
|
102
|
+
path: filePath,
|
|
103
|
+
createdAt: new Date().toISOString(),
|
|
104
|
+
});
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
await saveAudit(data);
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
/**
|
|
111
|
+
* Get all files written by Memorix for a project.
|
|
112
|
+
*/
|
|
113
|
+
export async function getProjectFiles(projectRoot: string): Promise<AuditEntry[]> {
|
|
114
|
+
const data = await loadAudit();
|
|
115
|
+
const projectId = getProjectId(projectRoot);
|
|
116
|
+
return data.projects[projectId]?.entries || [];
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
/**
|
|
120
|
+
* Remove a file from audit (when uninstalled).
|
|
121
|
+
*/
|
|
122
|
+
export async function removeFile(projectRoot: string, filePath: string): Promise<void> {
|
|
123
|
+
const data = await loadAudit();
|
|
124
|
+
const projectId = getProjectId(projectRoot);
|
|
125
|
+
|
|
126
|
+
if (!data.projects[projectId]) return;
|
|
127
|
+
|
|
128
|
+
data.projects[projectId].entries = data.projects[projectId].entries.filter(
|
|
129
|
+
(e) => e.path !== filePath
|
|
130
|
+
);
|
|
131
|
+
|
|
132
|
+
// If no entries left, remove the project
|
|
133
|
+
if (data.projects[projectId].entries.length === 0) {
|
|
134
|
+
delete data.projects[projectId];
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
await saveAudit(data);
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
/**
|
|
141
|
+
* Get all audit entries across all projects.
|
|
142
|
+
*/
|
|
143
|
+
export async function getAllAuditEntries(): Promise<
|
|
144
|
+
Array<{ projectId: string; entry: AuditEntry }>
|
|
145
|
+
> {
|
|
146
|
+
const data = await loadAudit();
|
|
147
|
+
const entries: Array<{ projectId: string; entry: AuditEntry }> = [];
|
|
148
|
+
|
|
149
|
+
for (const [projectId, project] of Object.entries(data.projects)) {
|
|
150
|
+
for (const entry of project.entries) {
|
|
151
|
+
entries.push({ projectId, entry });
|
|
152
|
+
}
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
return entries;
|
|
156
|
+
}
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { spawnSync } from 'node:child_process';
|
|
2
|
-
import { existsSync } from 'node:fs';
|
|
2
|
+
import { existsSync, realpathSync } from 'node:fs';
|
|
3
3
|
import { mkdir, readFile, writeFile } from 'node:fs/promises';
|
|
4
4
|
import { basename, dirname, resolve } from 'node:path';
|
|
5
5
|
import { homedir } from 'node:os';
|
|
@@ -580,11 +580,17 @@ function sanitizeServer(server: MCPServerEntry): AgentMcpCheck['server'] {
|
|
|
580
580
|
}
|
|
581
581
|
|
|
582
582
|
function normalizeProjectKey(projectRoot: string): string {
|
|
583
|
-
return
|
|
583
|
+
return canonicalProjectPath(projectRoot).toLowerCase();
|
|
584
584
|
}
|
|
585
585
|
|
|
586
586
|
function defaultClaudeProjectKey(projectRoot: string): string {
|
|
587
|
-
return
|
|
587
|
+
return canonicalProjectPath(projectRoot);
|
|
588
|
+
}
|
|
589
|
+
|
|
590
|
+
function canonicalProjectPath(projectRoot: string): string {
|
|
591
|
+
const resolved = resolve(projectRoot);
|
|
592
|
+
const canonical = existsSync(resolved) ? realpathSync(resolved) : resolved;
|
|
593
|
+
return canonical.replace(/\\/g, '/');
|
|
588
594
|
}
|
|
589
595
|
|
|
590
596
|
function getClaudeLocalConfigPath(): string {
|
|
@@ -131,6 +131,7 @@ export default defineCommand({
|
|
|
131
131
|
let bgRunning = false;
|
|
132
132
|
let bgPort = 0;
|
|
133
133
|
let bgPid = 0;
|
|
134
|
+
let backgroundHealthy = false;
|
|
134
135
|
|
|
135
136
|
try {
|
|
136
137
|
if (existsSync(bgStatePath)) {
|
|
@@ -148,7 +149,7 @@ export default defineCommand({
|
|
|
148
149
|
// Health check
|
|
149
150
|
try {
|
|
150
151
|
const http = await import('node:http');
|
|
151
|
-
|
|
152
|
+
backgroundHealthy = await new Promise<boolean>((resolve) => {
|
|
152
153
|
const req = http.request({ hostname: '127.0.0.1', port: bgPort, path: '/api/team', timeout: 3000 }, (res) => {
|
|
153
154
|
res.resume();
|
|
154
155
|
resolve(res.statusCode === 200);
|
|
@@ -157,7 +158,7 @@ export default defineCommand({
|
|
|
157
158
|
req.on('timeout', () => { req.destroy(); resolve(false); });
|
|
158
159
|
req.end();
|
|
159
160
|
});
|
|
160
|
-
if (
|
|
161
|
+
if (backgroundHealthy) {
|
|
161
162
|
lines.push(ok('Health check: OK'));
|
|
162
163
|
} else {
|
|
163
164
|
lines.push(warn('Health check: FAILED (process alive but not responding)'));
|
|
@@ -165,8 +166,9 @@ export default defineCommand({
|
|
|
165
166
|
}
|
|
166
167
|
} catch {
|
|
167
168
|
lines.push(warn('Health check: could not connect'));
|
|
169
|
+
issues.push('Background process is alive but its health endpoint could not be reached. Try "memorix background restart".');
|
|
168
170
|
}
|
|
169
|
-
report.mode = { type: 'background', pid: bgPid, port: bgPort, healthy:
|
|
171
|
+
report.mode = { type: 'background', pid: bgPid, port: bgPort, healthy: backgroundHealthy };
|
|
170
172
|
} else {
|
|
171
173
|
lines.push(info('Background control plane: not running'));
|
|
172
174
|
lines.push(info('Current invocation: CLI (stdio)'));
|
|
@@ -210,20 +212,49 @@ export default defineCommand({
|
|
|
210
212
|
} else {
|
|
211
213
|
lines.push(ok(`Mode: ${mode}`));
|
|
212
214
|
|
|
213
|
-
//
|
|
214
|
-
|
|
215
|
-
|
|
215
|
+
// A running control plane is the source of truth for runtime health.
|
|
216
|
+
// A fresh doctor process must not overwrite a live 401/402/timeout
|
|
217
|
+
// diagnosis with a static "configured = ready" result.
|
|
218
|
+
let controlPlaneEmbedding: {
|
|
219
|
+
status?: string;
|
|
220
|
+
provider?: { name?: string; dimensions?: number };
|
|
221
|
+
failure?: { kind?: string; retryAfterMs?: number };
|
|
222
|
+
} | undefined;
|
|
223
|
+
if (bgRunning) {
|
|
224
|
+
try {
|
|
225
|
+
const response = await fetch(`http://127.0.0.1:${bgPort}/health`, { signal: AbortSignal.timeout(3_000) });
|
|
226
|
+
if (response.ok) {
|
|
227
|
+
const health = await response.json() as { embedding?: typeof controlPlaneEmbedding };
|
|
228
|
+
controlPlaneEmbedding = health.embedding;
|
|
229
|
+
}
|
|
230
|
+
} catch { /* health is advisory; fall back to a local probe */ }
|
|
231
|
+
}
|
|
216
232
|
|
|
217
|
-
if (
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
233
|
+
if (controlPlaneEmbedding?.status === 'degraded') {
|
|
234
|
+
const kind = controlPlaneEmbedding.failure?.kind ?? 'upstream';
|
|
235
|
+
const retryAfterMs = controlPlaneEmbedding.failure?.retryAfterMs;
|
|
236
|
+
const retry = typeof retryAfterMs === 'number' && retryAfterMs > 0
|
|
237
|
+
? `; retry in ${Math.ceil(retryAfterMs / 1000)}s`
|
|
238
|
+
: '';
|
|
239
|
+
lines.push(warn(`Status: degraded in the running control plane (${kind}${retry})`));
|
|
240
|
+
lines.push(info('Search: degraded to BM25 fulltext until the provider recovers'));
|
|
241
|
+
issues.push('The running control plane has an unavailable embedding provider. Check provider credentials, billing, or connectivity.');
|
|
242
|
+
report.embedding = { mode, status: 'degraded', failure: kind };
|
|
222
243
|
} else {
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
244
|
+
const { getEmbeddingProvider } = await import('../../embedding/provider.js');
|
|
245
|
+
const provider = await getEmbeddingProvider();
|
|
246
|
+
|
|
247
|
+
if (provider) {
|
|
248
|
+
lines.push(ok(`Provider: ${provider.name} (${provider.dimensions}d)`));
|
|
249
|
+
lines.push(ok('Status: ready'));
|
|
250
|
+
lines.push(info('Search: hybrid (BM25 + vector)'));
|
|
251
|
+
report.embedding = { mode, status: 'ready', provider: provider.name, dimensions: provider.dimensions };
|
|
252
|
+
} else {
|
|
253
|
+
lines.push(warn('Status: temporarily unavailable'));
|
|
254
|
+
lines.push(info('Search: degraded to BM25 fulltext (no vector similarity)'));
|
|
255
|
+
issues.push(`Embedding mode is "${mode}" but provider failed to initialize. Check API keys/connectivity.`);
|
|
256
|
+
report.embedding = { mode, status: 'temporarily_unavailable' };
|
|
257
|
+
}
|
|
227
258
|
}
|
|
228
259
|
|
|
229
260
|
// Check cached embeddings count
|