memorix 1.8.4 → 1.8.6
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 +52 -0
- package/CLAUDE.md +3 -0
- package/README.md +8 -6
- package/README.zh-CN.md +5 -3
- package/assets/star-history-dark.svg +5 -5
- package/assets/star-history-light.svg +5 -5
- package/assets/star-history.json +35 -5
- package/dist/cli/index.js +1848 -754
- package/dist/dashboard/static/app.js +29 -885
- package/dist/dashboard/static/index.html +1 -5
- package/dist/dashboard/static/style.css +36 -503
- package/dist/index.d.ts +4 -4
- package/dist/index.js +286 -79
- package/dist/memcode/index.js +28035 -169
- package/dist/memcode-runtime/CHANGELOG.md +52 -0
- package/dist/sdk.js +103 -21
- package/dist/types.d.ts +1 -1
- package/docs/1.8.0-RELEASE-CANDIDATE-SPEC.md +22 -16
- package/docs/AGENT_OPERATOR_PLAYBOOK.md +2 -2
- package/docs/API_REFERENCE.md +10 -8
- package/docs/DASHBOARD.md +23 -0
- package/docs/DEVELOPMENT.md +2 -2
- package/docs/DOCKER.md +6 -3
- package/docs/INTEGRATIONS.md +2 -1
- package/docs/MODULES.md +46 -20
- package/docs/PERFORMANCE.md +1 -1
- package/docs/README.md +8 -7
- package/docs/SETUP.md +4 -3
- package/package.json +11 -11
- package/plugins/antigravity/memorix/plugin.json +1 -1
- package/plugins/antigravity/memorix/rules/memorix.md +5 -0
- package/plugins/claude/memorix/.claude-plugin/plugin.json +1 -1
- package/plugins/claude/memorix/skills/memorix-memory/SKILL.md +3 -0
- package/plugins/claude/memorix/skills/memorix-mini-skills/SKILL.md +2 -0
- package/plugins/claude/memorix/skills/memorix-orchestrate/SKILL.md +2 -0
- package/plugins/codebuddy/memorix/.codebuddy-plugin/plugin.json +1 -1
- package/plugins/codebuddy/memorix/skills/memorix-memory/SKILL.md +3 -0
- package/plugins/codebuddy/memorix/skills/memorix-mini-skills/SKILL.md +2 -0
- package/plugins/codebuddy/memorix/skills/memorix-orchestrate/SKILL.md +2 -0
- package/plugins/codex/memorix/.codex-plugin/plugin.json +1 -1
- package/plugins/codex/memorix/skills/memorix-memory/SKILL.md +3 -0
- package/plugins/codex/memorix/skills/memorix-mini-skills/SKILL.md +2 -0
- package/plugins/codex/memorix/skills/memorix-orchestrate/SKILL.md +2 -0
- package/plugins/copilot/memorix/plugin.json +1 -1
- package/plugins/copilot/memorix/skills/memorix-memory/SKILL.md +3 -0
- package/plugins/copilot/memorix/skills/memorix-mini-skills/SKILL.md +2 -0
- package/plugins/copilot/memorix/skills/memorix-orchestrate/SKILL.md +2 -0
- package/plugins/cursor/memorix/skills/memorix-memory/SKILL.md +3 -0
- package/plugins/cursor/memorix/skills/memorix-mini-skills/SKILL.md +2 -0
- package/plugins/cursor/memorix/skills/memorix-orchestrate/SKILL.md +2 -0
- package/plugins/gemini/memorix/GEMINI.md +5 -0
- package/plugins/gemini/memorix/gemini-extension.json +1 -1
- package/plugins/hermes/memorix/plugin.yaml +7 -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/plugins/pi/memorix/skills/memorix-memory/SKILL.md +3 -0
- package/plugins/pi/memorix/skills/memorix-mini-skills/SKILL.md +2 -0
- package/plugins/pi/memorix/skills/memorix-orchestrate/SKILL.md +2 -0
- package/server.json +2 -2
- package/src/cli/command-guide.ts +52 -1
- package/src/cli/commands/agent-integrations.ts +1 -0
- package/src/cli/commands/background.ts +166 -6
- package/src/cli/commands/hooks-install.ts +9 -2
- package/src/cli/commands/hooks-preview.ts +5 -0
- package/src/cli/commands/integrate.ts +2 -0
- package/src/cli/commands/serve-http.ts +73 -80
- package/src/cli/commands/serve.ts +95 -93
- package/src/cli/commands/setup.ts +11 -1
- package/src/cli/commands/uninstall.ts +24 -2
- package/src/cli/index.ts +1 -1
- package/src/codegraph/task-lens.ts +72 -7
- package/src/dashboard/static/index.html +1 -5
- package/src/dashboard/static/style.css +36 -503
- package/src/hooks/installers/index.ts +58 -0
- package/src/hooks/official-skills.ts +13 -6
- package/src/hooks/types.ts +2 -0
- package/src/integrations/registry.ts +9 -0
- package/src/memory/graph.ts +2 -2
- package/src/server/mcp-diagnostics.ts +9 -7
- package/src/server/modern-mcp-bridge.ts +108 -0
- package/src/server/stdio-startup-gate.ts +41 -2
- package/src/server/tool-profile.ts +7 -6
- package/src/server.ts +1 -1
- package/src/store/orama-store.ts +2 -2
- package/src/types.ts +1 -1
- package/src/wiki/types.ts +2 -2
- package/src/workspace/mcp-adapters/workbuddy.ts +71 -0
|
@@ -1,5 +1,10 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* memorix serve
|
|
2
|
+
* memorix serve - Start MCP Server on stdio transport.
|
|
3
|
+
*
|
|
4
|
+
* The official MCP v2 stdio entry owns modern discovery, per-request metadata,
|
|
5
|
+
* legacy compatibility, and protocol lifecycle. Memorix keeps its startup
|
|
6
|
+
* gate in front of that entry so early claim-less discovery remains compatible
|
|
7
|
+
* with clients shipped before the 2026 protocol revision.
|
|
3
8
|
*/
|
|
4
9
|
|
|
5
10
|
import { defineCommand } from 'citty';
|
|
@@ -7,6 +12,7 @@ import { resolveToolProfile } from '../../server/tool-profile.js';
|
|
|
7
12
|
import { getMcpServerInfo } from '../../server/mcp-discovery.js';
|
|
8
13
|
import { StdioStartupGate } from '../../server/stdio-startup-gate.js';
|
|
9
14
|
import { mcpFileUriToPath } from '../mcp-root-path.js';
|
|
15
|
+
import type { ModernMcpBridge } from '../../server/modern-mcp-bridge.js';
|
|
10
16
|
|
|
11
17
|
export default defineCommand({
|
|
12
18
|
meta: {
|
|
@@ -31,40 +37,42 @@ export default defineCommand({
|
|
|
31
37
|
},
|
|
32
38
|
},
|
|
33
39
|
run: async ({ args }) => {
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
40
|
+
const { serveStdio, StdioServerTransport } = await import('@modelcontextprotocol/server/stdio');
|
|
41
|
+
const { createModernMcpBridge } = await import('../../server/modern-mcp-bridge.js');
|
|
42
|
+
const { detectProject, findGitInSubdirs, isSystemDirectory } = await import('../../project/detector.js');
|
|
43
|
+
const { homedir } = await import('node:os');
|
|
44
|
+
const { writeLastProjectRoot } = await import('../../project/launch-root.js');
|
|
45
|
+
const { resolveServeProject } = await import('./serve-shared.js');
|
|
46
|
+
|
|
47
|
+
// Take ownership of stdin before project/config/runtime initialization.
|
|
48
|
+
// The gate answers the old claim-less discover probe and queues every
|
|
49
|
+
// other line until the official v2 transport is listening.
|
|
50
|
+
let stdioHandle: { close: () => Promise<void> } | undefined;
|
|
51
|
+
let stdinEndedBeforeHandle = false;
|
|
37
52
|
const startupGate = new StdioStartupGate({
|
|
38
53
|
stdin: process.stdin,
|
|
39
54
|
stdout: process.stdout,
|
|
40
55
|
serverInfo: getMcpServerInfo(),
|
|
41
56
|
onError: (error) => {
|
|
42
57
|
console.error(`[memorix] stdio startup gate error: ${error.message}`);
|
|
43
|
-
// The stream is no longer safe to replay after an input-limit or I/O
|
|
44
|
-
// failure. Exit instead of leaving a half-connected MCP process alive.
|
|
45
58
|
process.exit(1);
|
|
46
59
|
},
|
|
47
60
|
onEnd: () => {
|
|
48
|
-
console.error('[memorix] stdin closed
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
61
|
+
console.error('[memorix] stdin closed - exiting');
|
|
62
|
+
if (stdioHandle) {
|
|
63
|
+
void stdioHandle.close().catch((error) => {
|
|
64
|
+
console.error(`[memorix] stdio close error: ${error instanceof Error ? error.message : String(error)}`);
|
|
65
|
+
});
|
|
66
|
+
} else {
|
|
67
|
+
stdinEndedBeforeHandle = true;
|
|
68
|
+
}
|
|
52
69
|
},
|
|
53
70
|
});
|
|
54
71
|
startupGate.start();
|
|
55
72
|
|
|
56
|
-
|
|
57
|
-
'@modelcontextprotocol/sdk/server/stdio.js'
|
|
58
|
-
);
|
|
59
|
-
const { createMemorixServer } = await import('../../server.js');
|
|
60
|
-
const { detectProject, findGitInSubdirs, isSystemDirectory } = await import('../../project/detector.js');
|
|
61
|
-
const { homedir } = await import('node:os');
|
|
62
|
-
const { resolveServeProject } = await import('./serve-shared.js');
|
|
63
|
-
|
|
64
|
-
// Priority: explicit --cwd arg > MEMORIX_PROJECT_ROOT env > INIT_CWD (npm lifecycle) > process.cwd()
|
|
73
|
+
// Priority: explicit --cwd arg > MEMORIX_PROJECT_ROOT > INIT_CWD > process.cwd().
|
|
65
74
|
let safeCwd: string;
|
|
66
75
|
try { safeCwd = process.cwd(); } catch { safeCwd = homedir(); }
|
|
67
|
-
|
|
68
76
|
const resolution = resolveServeProject(
|
|
69
77
|
{
|
|
70
78
|
cwdArg: args.cwd,
|
|
@@ -75,101 +83,95 @@ export default defineCommand({
|
|
|
75
83
|
},
|
|
76
84
|
{ detectProject, findGitInSubdirs, isSystemDirectory },
|
|
77
85
|
);
|
|
78
|
-
|
|
79
|
-
for (const message of resolution.messages) {
|
|
80
|
-
console.error(message);
|
|
81
|
-
}
|
|
86
|
+
for (const message of resolution.messages) console.error(message);
|
|
82
87
|
|
|
83
88
|
if (!resolution.detectedProject) {
|
|
84
89
|
console.error(`[memorix] [WARN] ${resolution.error}`);
|
|
85
|
-
console.error(
|
|
86
|
-
console.error(
|
|
87
|
-
// Don't exit — allow deferred binding via session_start or MCP roots (fixes Cursor stdio #75)
|
|
90
|
+
console.error('[memorix] Starting in deferred-binding mode - bind with memorix_session_start(projectRoot=...) when needed.');
|
|
91
|
+
console.error('[memorix] For non-git directories, use --allow-untracked to enable untracked/ fallback.');
|
|
88
92
|
}
|
|
89
93
|
|
|
90
94
|
const detected = resolution.detectedProject;
|
|
91
95
|
const projectRoot = resolution.projectRoot;
|
|
92
|
-
if (detected)
|
|
93
|
-
const { writeLastProjectRoot } = await import('../../project/launch-root.js');
|
|
94
|
-
writeLastProjectRoot(detected.rootPath);
|
|
95
|
-
}
|
|
96
|
+
if (detected) writeLastProjectRoot(detected.rootPath);
|
|
96
97
|
|
|
97
|
-
// Always register ALL tools BEFORE connecting transport.
|
|
98
|
-
// This ensures tools/list returns the full tool set immediately on connect.
|
|
99
|
-
// When no project detected, use deferred binding (allowUntrackedFallback=false, deferProjectInitUntilBound=true)
|
|
100
98
|
const allowUntracked = args['allow-untracked'] ?? false;
|
|
101
|
-
const toolProfile = resolveToolProfile({
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
:
|
|
105
|
-
|
|
106
|
-
const transport = new StdioServerTransport(startupGate.input, process.stdout);
|
|
107
|
-
await server.connect(transport);
|
|
108
|
-
startupGate.markReady();
|
|
109
|
-
|
|
110
|
-
console.error(`[memorix] MCP Server running on stdio (project: ${projectId})`);
|
|
111
|
-
console.error(`[memorix] Project root: ${detected?.rootPath ?? projectRoot}`);
|
|
99
|
+
const toolProfile = resolveToolProfile({
|
|
100
|
+
explicit: args.mode,
|
|
101
|
+
envValue: process.env.MEMORIX_MODE,
|
|
102
|
+
fallback: 'micro',
|
|
103
|
+
});
|
|
112
104
|
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
105
|
+
const transport = new StdioServerTransport(startupGate.input, process.stdout);
|
|
106
|
+
const createBridge = async (): Promise<ModernMcpBridge> => {
|
|
107
|
+
const bridge = await createModernMcpBridge({
|
|
108
|
+
projectRoot,
|
|
109
|
+
allowUntrackedFallback: allowUntracked,
|
|
110
|
+
deferProjectInitUntilBound: !allowUntracked,
|
|
111
|
+
deferProjectRuntimeInit: true,
|
|
112
|
+
toolProfile,
|
|
113
|
+
});
|
|
121
114
|
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
115
|
+
const tryRootsSwitch = async (): Promise<void> => {
|
|
116
|
+
try {
|
|
117
|
+
if (bridge.memorix.isExplicitlyBound()) return;
|
|
118
|
+
const { roots } = await bridge.server.listRoots();
|
|
119
|
+
for (const root of roots ?? []) {
|
|
120
|
+
if (!root.uri.startsWith('file://')) continue;
|
|
121
|
+
const rootPath = mcpFileUriToPath(root.uri);
|
|
122
|
+
if (!rootPath) continue;
|
|
126
123
|
|
|
127
|
-
|
|
128
|
-
if (rootDetected) {
|
|
129
|
-
const switched = await switchProject(rootPath);
|
|
124
|
+
const switched = await bridge.memorix.switchProject(rootPath, 'mcp-roots');
|
|
130
125
|
if (switched) {
|
|
131
|
-
console.error(`[memorix] [UPDATED] Project updated via MCP roots: ${
|
|
126
|
+
console.error(`[memorix] [UPDATED] Project updated via MCP roots: ${rootPath}`);
|
|
127
|
+
return;
|
|
132
128
|
}
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
// Root itself has no .git — try its subdirs
|
|
136
|
-
const subGit = findGitInSubdirs(rootPath);
|
|
137
|
-
if (subGit) {
|
|
138
|
-
const switched = await switchProject(subGit);
|
|
139
|
-
if (switched) {
|
|
129
|
+
const subGit = findGitInSubdirs(rootPath);
|
|
130
|
+
if (subGit && await bridge.memorix.switchProject(subGit, 'mcp-roots')) {
|
|
140
131
|
console.error(`[memorix] [UPDATED] Project updated via MCP roots (subdir): ${subGit}`);
|
|
132
|
+
return;
|
|
141
133
|
}
|
|
142
|
-
return;
|
|
143
134
|
}
|
|
135
|
+
} catch (error) {
|
|
136
|
+
console.error(`[memorix] MCP roots not available (${error instanceof Error ? error.message : 'unsupported'})`);
|
|
144
137
|
}
|
|
145
|
-
}
|
|
146
|
-
// Client doesn't support roots — that's OK, fall back to existing detection
|
|
147
|
-
console.error(`[memorix] MCP roots not available (${(err as Error).message ?? 'unsupported'})`);
|
|
148
|
-
}
|
|
149
|
-
};
|
|
150
|
-
|
|
151
|
-
// Do NOT proactively call listRoots() after connect — this violates MCP SEP-2260
|
|
152
|
-
// which requires server-initiated requests to be associated with a client request.
|
|
153
|
-
// Some clients (e.g. Codex) treat standalone roots/list as unexpected and may
|
|
154
|
-
// fail to inject MCP tools. Instead, rely on:
|
|
155
|
-
// 1. RootsListChangedNotification (client-initiated, then we respond)
|
|
156
|
-
// 2. memorix_session_start({ projectRoot }) for explicit binding
|
|
157
|
-
// 3. cwd-based detection as fallback (already done in deferred-binding)
|
|
138
|
+
};
|
|
158
139
|
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
server.server.setNotificationHandler(RootsListChangedNotificationSchema, async () => {
|
|
163
|
-
console.error(`[memorix] Roots changed — re-detecting project...`);
|
|
140
|
+
// roots/list is a legacy-era request. The v2 SDK rejects it on the
|
|
141
|
+
// modern era, while retaining this handler keeps existing IDE clients.
|
|
142
|
+
bridge.server.setNotificationHandler('notifications/roots/list_changed', async () => {
|
|
164
143
|
await tryRootsSwitch();
|
|
165
144
|
});
|
|
166
|
-
} catch { /* notification handler setup is optional */ }
|
|
167
145
|
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
146
|
+
const deferredInitTimer = setTimeout(() => {
|
|
147
|
+
bridge.memorix.deferredInit().catch((error) => {
|
|
148
|
+
console.error(`[memorix] Deferred init error: ${error instanceof Error ? error.message : String(error)}`);
|
|
149
|
+
});
|
|
150
|
+
}, 5_000);
|
|
151
|
+
deferredInitTimer.unref?.();
|
|
152
|
+
|
|
153
|
+
const originalClose = bridge.close.bind(bridge);
|
|
154
|
+
bridge.close = async () => {
|
|
155
|
+
clearTimeout(deferredInitTimer);
|
|
156
|
+
await originalClose();
|
|
157
|
+
};
|
|
158
|
+
return bridge;
|
|
159
|
+
};
|
|
160
|
+
|
|
161
|
+
stdioHandle = serveStdio(createBridge, {
|
|
162
|
+
transport,
|
|
163
|
+
legacy: 'serve',
|
|
164
|
+
onerror: (error) => console.error(`[memorix] stdio MCP error: ${error.message}`),
|
|
165
|
+
});
|
|
166
|
+
startupGate.markReady();
|
|
167
|
+
if (stdinEndedBeforeHandle) {
|
|
168
|
+
void stdioHandle.close().catch((error) => {
|
|
169
|
+
console.error(`[memorix] stdio close error: ${error instanceof Error ? error.message : String(error)}`);
|
|
170
|
+
});
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
console.error(`[memorix] MCP Server running on stdio (profile: ${toolProfile}, project: ${detected?.id ?? 'deferred'})`);
|
|
174
|
+
console.error(`[memorix] Project root: ${detected?.rootPath ?? projectRoot}`);
|
|
173
175
|
import('../update-checker.js').then(m => m.checkForUpdates()).catch(() => {});
|
|
174
176
|
},
|
|
175
177
|
});
|
|
@@ -31,6 +31,7 @@ import { KiroMCPAdapter } from '../../workspace/mcp-adapters/kiro.js';
|
|
|
31
31
|
import { OpenCodeMCPAdapter } from '../../workspace/mcp-adapters/opencode.js';
|
|
32
32
|
import { TraeMCPAdapter } from '../../workspace/mcp-adapters/trae.js';
|
|
33
33
|
import { DshMCPAdapter } from '../../workspace/mcp-adapters/dsh.js';
|
|
34
|
+
import { WorkbuddyMCPAdapter } from '../../workspace/mcp-adapters/workbuddy.js';
|
|
34
35
|
import { CodeBuddyMCPAdapter } from '../../workspace/mcp-adapters/codebuddy.js';
|
|
35
36
|
import { getSetupIntegrationRows as readSetupIntegrationRows } from '../../integrations/registry.js';
|
|
36
37
|
import { OFFICIAL_MEMORIX_SKILLS } from '../../hooks/official-skills.js';
|
|
@@ -192,6 +193,7 @@ const SUPPORTED_SETUP_AGENTS: AgentName[] = [
|
|
|
192
193
|
'kiro',
|
|
193
194
|
'trae',
|
|
194
195
|
'dsh',
|
|
196
|
+
'workbuddy',
|
|
195
197
|
'pi',
|
|
196
198
|
];
|
|
197
199
|
|
|
@@ -405,6 +407,7 @@ export function getMcpAdapter(agent: McpConfigAgent): MCPConfigAdapter {
|
|
|
405
407
|
opencode: new OpenCodeMCPAdapter(),
|
|
406
408
|
trae: new TraeMCPAdapter(),
|
|
407
409
|
dsh: new DshMCPAdapter(),
|
|
410
|
+
workbuddy: new WorkbuddyMCPAdapter(),
|
|
408
411
|
};
|
|
409
412
|
return adapters[agent];
|
|
410
413
|
}
|
|
@@ -1611,7 +1614,14 @@ export async function installAgentSetup(agent: AgentName, plan: SetupPlan, globa
|
|
|
1611
1614
|
}
|
|
1612
1615
|
} else if (plan.actions.includes('hooks')) {
|
|
1613
1616
|
const result = await installHooks(agent, targetRoot, global);
|
|
1614
|
-
|
|
1617
|
+
const note = (result.generated as Record<string, unknown>)?.note;
|
|
1618
|
+
if (result.configPath) {
|
|
1619
|
+
p.log.success(`${agent}: integration files -> ${result.configPath}`);
|
|
1620
|
+
} else if (note) {
|
|
1621
|
+
p.log.info(`${agent}: ${note}`);
|
|
1622
|
+
} else {
|
|
1623
|
+
p.log.info(`${agent}: no hooks or guidance are available at this scope`);
|
|
1624
|
+
}
|
|
1615
1625
|
} else if (plan.actions.includes('project-guidance')) {
|
|
1616
1626
|
const rulesPath = await installAgentGuidance(agent, targetRoot, global);
|
|
1617
1627
|
p.log.success(`${agent}: guidance -> ${rulesPath}`);
|
|
@@ -24,7 +24,7 @@ import * as os from 'node:os';
|
|
|
24
24
|
|
|
25
25
|
// ── MCP config paths (documented, not modified) ────────────────────
|
|
26
26
|
|
|
27
|
-
interface MCPConfigEntry {
|
|
27
|
+
export interface MCPConfigEntry {
|
|
28
28
|
agent: string;
|
|
29
29
|
path: string;
|
|
30
30
|
kind: 'project' | 'global';
|
|
@@ -66,7 +66,7 @@ function checkDshPatch(p: string): boolean {
|
|
|
66
66
|
}
|
|
67
67
|
|
|
68
68
|
/** Detects memorix MCP config entries in both global and project-level paths. */
|
|
69
|
-
function getMCPConfigEntries(home: string, cwd: string): MCPConfigEntry[] {
|
|
69
|
+
export function getMCPConfigEntries(home: string, cwd: string): MCPConfigEntry[] {
|
|
70
70
|
const entries: MCPConfigEntry[] = [];
|
|
71
71
|
|
|
72
72
|
// ── Global paths ──
|
|
@@ -236,6 +236,28 @@ function getMCPConfigEntries(home: string, cwd: string): MCPConfigEntry[] {
|
|
|
236
236
|
note: 'Remove the memory-memorix insert row from this patch file.',
|
|
237
237
|
});
|
|
238
238
|
|
|
239
|
+
// WorkBuddy — global
|
|
240
|
+
const workbuddyCfg = path.join(home, '.workbuddy', 'mcp.json');
|
|
241
|
+
entries.push({
|
|
242
|
+
agent: 'WorkBuddy',
|
|
243
|
+
path: workbuddyCfg,
|
|
244
|
+
kind: 'global',
|
|
245
|
+
format: 'json',
|
|
246
|
+
detected: checkJSON(workbuddyCfg),
|
|
247
|
+
note: 'Remove the "memorix" key from mcpServers in this file.',
|
|
248
|
+
});
|
|
249
|
+
|
|
250
|
+
// WorkBuddy — project level (project-only installs must be discoverable too)
|
|
251
|
+
const workbuddyProjectCfg = path.join(cwd, '.workbuddy', 'mcp.json');
|
|
252
|
+
entries.push({
|
|
253
|
+
agent: 'WorkBuddy',
|
|
254
|
+
path: workbuddyProjectCfg,
|
|
255
|
+
kind: 'project',
|
|
256
|
+
format: 'json',
|
|
257
|
+
detected: checkJSON(workbuddyProjectCfg),
|
|
258
|
+
note: 'Remove the "memorix" key from mcpServers in this file.',
|
|
259
|
+
});
|
|
260
|
+
|
|
239
261
|
return entries;
|
|
240
262
|
}
|
|
241
263
|
|
package/src/cli/index.ts
CHANGED
|
@@ -389,7 +389,7 @@ const main = defineCommand({
|
|
|
389
389
|
console.error(' integrate Install one IDE integration into the current repo');
|
|
390
390
|
console.error(' status Show project info + stats');
|
|
391
391
|
console.error(' dashboard Open standalone dashboard (read-mostly)');
|
|
392
|
-
console.error(' hooks
|
|
392
|
+
console.error(' hooks Install, inspect, or remove agent hooks');
|
|
393
393
|
console.error(' cleanup Remove old memories');
|
|
394
394
|
console.error(' purge Retire all memories (project by default, --all for everything)');
|
|
395
395
|
console.error(' sync Rules/workspace sync plus interactive wizard');
|
|
@@ -143,19 +143,73 @@ const KEYWORDS: Record<Exclude<TaskLensId, 'general'>, string[]> = {
|
|
|
143
143
|
// Continuation is a delivery intent, not a task lens. A request can both resume
|
|
144
144
|
// prior work and be a bugfix, feature, or release task, so callers keep the
|
|
145
145
|
// normal lens and add a bounded prior-work projection separately.
|
|
146
|
-
|
|
146
|
+
//
|
|
147
|
+
// Matching triggers buildHookProjectContext() and a full prior-work brief,
|
|
148
|
+
// which measurably costs 2-3x an ordinary prompt (~3s → ~7.5s locally, 15s+
|
|
149
|
+
// under load) — enough to blow the 10s UserPromptSubmit hook timeout, after
|
|
150
|
+
// which Claude Code discards the injection entirely.
|
|
151
|
+
//
|
|
152
|
+
// The split below is about *evidence of intent*, not about the words alone:
|
|
153
|
+
//
|
|
154
|
+
// EXPLICIT — handoff vocabulary is deliberate on its own. "交接" and
|
|
155
|
+
// "handoff" are not said casually, so a bare mention counts.
|
|
156
|
+
// AMBIGUOUS — "继续" / "continue" / "恢复" are everyday verbs. Bare, they
|
|
157
|
+
// are filler meaning "go on" (and "恢复" usually means "restore
|
|
158
|
+
// the service" in ops work). Carrying a real task they are a
|
|
159
|
+
// genuine resume request — `memorix resume "继续处理发布阻塞问题"`
|
|
160
|
+
// is documented usage — so they require substantive content.
|
|
161
|
+
const EXPLICIT_HANDOFF_KEYWORDS = [
|
|
162
|
+
'handoff',
|
|
163
|
+
'hand off',
|
|
164
|
+
'hand over',
|
|
165
|
+
'previous session',
|
|
166
|
+
'last session',
|
|
167
|
+
'pick up where',
|
|
168
|
+
'交接', // 覆盖「任务交接」「工作交接」
|
|
169
|
+
'接手',
|
|
170
|
+
'上次会话',
|
|
171
|
+
'上次的会话',
|
|
172
|
+
];
|
|
173
|
+
|
|
174
|
+
const AMBIGUOUS_CONTINUATION_KEYWORDS = [
|
|
147
175
|
'continue',
|
|
148
176
|
'resume',
|
|
149
|
-
'pick up',
|
|
150
177
|
'carry on',
|
|
151
|
-
'
|
|
178
|
+
'pick up',
|
|
152
179
|
'继续',
|
|
153
|
-
'接手',
|
|
154
180
|
'恢复',
|
|
155
181
|
'延续',
|
|
156
|
-
'上次会话',
|
|
157
182
|
];
|
|
158
183
|
|
|
184
|
+
/**
|
|
185
|
+
* Characters that carry no task meaning on their own: punctuation, whitespace,
|
|
186
|
+
* Chinese modal particles, and the generic scaffolding words users wrap around
|
|
187
|
+
* a bare "继续" ("任务继续", "继续一下").
|
|
188
|
+
*/
|
|
189
|
+
const TASK_FILLER_WORDS = /\b(?:please|task|work|the|a|an)\b/giu;
|
|
190
|
+
const TASK_FILLER_PHRASES = /(?:吧|了|呢|啊|吗|嘛|呀|哦|噢|一下|一起|接着|请|帮我)/gu;
|
|
191
|
+
|
|
192
|
+
/** Minimum residual code points that count as a real task description. */
|
|
193
|
+
const MIN_TASK_SUBSTANCE = 2;
|
|
194
|
+
|
|
195
|
+
function hasSubstantiveTask(text: string, keyword: string): boolean {
|
|
196
|
+
const withoutKeyword = text.replace(
|
|
197
|
+
new RegExp(keyword.replace(/[.*+?^${}()|[\]\\]/g, '\\$&'), 'gi'),
|
|
198
|
+
' ',
|
|
199
|
+
);
|
|
200
|
+
const residual = withoutKeyword
|
|
201
|
+
// English fillers are removed as whole words, so `a` in `API` and `work`
|
|
202
|
+
// in `workflow` remain meaningful task text.
|
|
203
|
+
.replace(TASK_FILLER_WORDS, ' ')
|
|
204
|
+
.replace(TASK_FILLER_PHRASES, ' ')
|
|
205
|
+
.replace(/[\s\p{P}]/gu, '');
|
|
206
|
+
|
|
207
|
+
// These are scaffolding-only phrases, not tasks. Keep `工作流` intact by
|
|
208
|
+
// checking the complete residual instead of removing `工作` as a prefix.
|
|
209
|
+
if (/^(?:任务|工作|一下|一起|接着)$/u.test(residual)) return false;
|
|
210
|
+
return Array.from(residual).length >= MIN_TASK_SUBSTANCE;
|
|
211
|
+
}
|
|
212
|
+
|
|
159
213
|
const LENS_PRIORITY: Exclude<TaskLensId, 'general'>[] = [
|
|
160
214
|
'bugfix',
|
|
161
215
|
'release',
|
|
@@ -250,11 +304,22 @@ export function resolveTaskLens(task?: string): TaskLens {
|
|
|
250
304
|
* Detect when the caller is asking to continue existing work. This stays
|
|
251
305
|
* separate from task-lens routing so "continue fixing the timeout" remains a
|
|
252
306
|
* bugfix, while still receiving a compact prior-work brief.
|
|
307
|
+
*
|
|
308
|
+
* Explicit handoff vocabulary counts on its own. Everyday continuation verbs
|
|
309
|
+
* additionally require a substantive task, so a bare "继续" / "continue" — the
|
|
310
|
+
* single most common thing a user types to mean "go on" — stays on the cheap
|
|
311
|
+
* path instead of paying for a full brief on every turn.
|
|
253
312
|
*/
|
|
254
313
|
export function isContinuationTask(task?: string): boolean {
|
|
255
314
|
const normalized = (task ?? '').trim().toLowerCase();
|
|
256
|
-
|
|
257
|
-
|
|
315
|
+
if (normalized.length === 0) return false;
|
|
316
|
+
|
|
317
|
+
if (EXPLICIT_HANDOFF_KEYWORDS.some((keyword) => containsTaskKeyword(normalized, keyword))) {
|
|
318
|
+
return true;
|
|
319
|
+
}
|
|
320
|
+
|
|
321
|
+
return AMBIGUOUS_CONTINUATION_KEYWORDS.some((keyword) =>
|
|
322
|
+
containsTaskKeyword(normalized, keyword) && hasSubstantiveTask(normalized, keyword),
|
|
258
323
|
);
|
|
259
324
|
}
|
|
260
325
|
|
|
@@ -142,7 +142,7 @@
|
|
|
142
142
|
<path d="M23 21v-2a4 4 0 0 0-3-3.87"/>
|
|
143
143
|
<path d="M16 3.13a4 4 0 0 1 0 7.75"/>
|
|
144
144
|
</svg>
|
|
145
|
-
<span class="nav-label">
|
|
145
|
+
<span class="nav-label">Coordination</span>
|
|
146
146
|
</button>
|
|
147
147
|
</div>
|
|
148
148
|
<div class="sidebar-spacer"></div>
|
|
@@ -211,10 +211,6 @@
|
|
|
211
211
|
|
|
212
212
|
<!-- Apache ECharts 5 — Knowledge Graph renderer -->
|
|
213
213
|
<script src="https://cdn.jsdelivr.net/npm/echarts@5.5.1/dist/echarts.min.js"></script>
|
|
214
|
-
<!-- Cytoscape.js + Dagre — legacy entity-graph fallback -->
|
|
215
|
-
<script src="https://unpkg.com/cytoscape@3.30.4/dist/cytoscape.min.js"></script>
|
|
216
|
-
<script src="https://unpkg.com/dagre@0.8.5/dist/dagre.min.js"></script>
|
|
217
|
-
<script src="https://unpkg.com/cytoscape-dagre@2.5.0/cytoscape-dagre.js"></script>
|
|
218
214
|
<script>
|
|
219
215
|
// Cache-bust app.js by appending mtime-based version so edits always hit the browser.
|
|
220
216
|
(function () {
|