memorix 1.1.12 → 1.2.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/CHANGELOG.md +28 -0
- package/README.md +5 -2
- package/README.zh-CN.md +5 -2
- package/dist/cli/index.js +36575 -31127
- package/dist/cli/index.js.map +1 -1
- package/dist/dashboard/static/app.js +50 -30
- package/dist/index.js +5348 -674
- package/dist/index.js.map +1 -1
- package/dist/maintenance-runner.d.ts +1 -1
- package/dist/maintenance-runner.js +3661 -293
- package/dist/maintenance-runner.js.map +1 -1
- package/dist/memcode-runtime/CHANGELOG.md +28 -0
- package/dist/memcode-runtime/package.json +1 -0
- package/dist/sdk.d.ts +1 -1
- package/dist/sdk.js +5346 -672
- package/dist/sdk.js.map +1 -1
- package/docs/1.2.0-CLAIM-LEDGER.md +72 -0
- package/docs/1.2.0-CODE-STATE.md +61 -0
- package/docs/1.2.0-DEVELOPMENT-CHARTER.md +255 -0
- package/docs/1.2.0-DYNAMIC-LIFECYCLE.md +71 -0
- package/docs/1.2.0-EVALUATION-HARNESS.md +51 -0
- package/docs/1.2.0-IMPLEMENTATION-PLAN.md +554 -0
- package/docs/1.2.0-KNOWLEDGE-WORKFLOW-RESEARCH.md +205 -0
- package/docs/1.2.0-KNOWLEDGE-WORKSPACE.md +68 -0
- package/docs/1.2.0-PRODUCT-STORY.md +234 -0
- package/docs/1.2.0-PROVIDER-QUALITY.md +189 -0
- package/docs/1.2.0-WORKFLOW-INHERITANCE.md +101 -0
- package/docs/1.2.0-WORKSET-RETRIEVAL.md +80 -0
- package/docs/AGENT_OPERATOR_PLAYBOOK.md +7 -4
- package/docs/API_REFERENCE.md +25 -6
- package/docs/CONFIGURATION.md +21 -3
- package/docs/DEVELOPMENT.md +3 -10
- package/docs/INTEGRATIONS.md +1 -1
- package/docs/README.md +17 -2
- package/docs/SETUP.md +3 -1
- package/docs/dev-log/progress.txt +115 -63
- package/llms-full.txt +16 -2
- package/llms.txt +9 -4
- package/package.json +3 -2
- package/plugins/codex/memorix/.codex-plugin/plugin.json +1 -1
- package/src/cli/capability-map.ts +1 -0
- package/src/cli/commands/agent-integrations.ts +384 -2
- package/src/cli/commands/codegraph.ts +112 -9
- package/src/cli/commands/context.ts +2 -0
- package/src/cli/commands/doctor.ts +73 -4
- package/src/cli/commands/knowledge.ts +282 -0
- package/src/cli/commands/serve-http.ts +12 -1
- package/src/cli/commands/setup.ts +13 -1
- package/src/cli/index.ts +3 -1
- package/src/cli/tui/App.tsx +1 -1
- package/src/cli/tui/Panels.tsx +8 -8
- package/src/cli/tui/theme.ts +1 -1
- package/src/cli/tui/views/GraphView.tsx +8 -7
- package/src/cli/tui/views/KnowledgeView.tsx +9 -9
- package/src/codegraph/auto-context.ts +171 -9
- package/src/codegraph/code-state.ts +95 -0
- package/src/codegraph/context-pack.ts +82 -1
- package/src/codegraph/external-provider.ts +581 -0
- package/src/codegraph/lite-provider.ts +64 -19
- package/src/codegraph/project-context.ts +9 -1
- package/src/codegraph/store.ts +154 -6
- package/src/codegraph/types.ts +117 -0
- package/src/config/resolved-config.ts +28 -0
- package/src/config/toml-loader.ts +3 -0
- package/src/config/yaml-loader.ts +6 -0
- package/src/dashboard/server.ts +15 -1
- package/src/evaluation/workset-evaluation.ts +120 -0
- package/src/hooks/handler.ts +48 -6
- package/src/knowledge/claim-store.ts +267 -0
- package/src/knowledge/claims.ts +537 -0
- package/src/knowledge/markdown.ts +129 -0
- package/src/knowledge/types.ts +157 -0
- package/src/knowledge/wiki.ts +524 -0
- package/src/knowledge/workflow-store.ts +168 -0
- package/src/knowledge/workflow-types.ts +95 -0
- package/src/knowledge/workflows.ts +743 -0
- package/src/knowledge/workset.ts +515 -0
- package/src/knowledge/workspace-store.ts +220 -0
- package/src/knowledge/workspace-types.ts +106 -0
- package/src/knowledge/workspace.ts +220 -0
- package/src/memory/observations.ts +19 -0
- package/src/runtime/control-plane-maintenance.ts +5 -0
- package/src/runtime/isolated-maintenance.ts +5 -0
- package/src/runtime/lifecycle-status.ts +102 -0
- package/src/runtime/lifecycle.ts +107 -0
- package/src/runtime/maintenance-jobs.ts +5 -0
- package/src/runtime/project-maintenance.ts +190 -0
- package/src/server/tool-profile.ts +3 -2
- package/src/server.ts +354 -14
- package/src/store/file-lock.ts +24 -4
- package/src/store/sqlite-db.ts +307 -0
- package/src/wiki/generator.ts +4 -2
- package/src/wiki/knowledge-graph.ts +7 -4
- package/src/wiki/types.ts +16 -4
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { spawnSync } from 'node:child_process';
|
|
1
2
|
import { existsSync } from 'node:fs';
|
|
2
3
|
import { mkdir, readFile, writeFile } from 'node:fs/promises';
|
|
3
4
|
import { basename, dirname, resolve } from 'node:path';
|
|
@@ -9,9 +10,12 @@ import {
|
|
|
9
10
|
buildMemorixServer,
|
|
10
11
|
getMcpAdapter,
|
|
11
12
|
getSetupAgentTargets,
|
|
13
|
+
installPluginPackage,
|
|
12
14
|
installMcpConfig,
|
|
15
|
+
tryInstallCodexPlugin,
|
|
13
16
|
type McpConfigAgent,
|
|
14
17
|
} from './setup.js';
|
|
18
|
+
import { getCliVersion } from '../version.js';
|
|
15
19
|
|
|
16
20
|
export type AgentIntegrationScope = 'local' | 'project' | 'global' | 'all';
|
|
17
21
|
export type AgentIntegrationStatus = 'ok' | 'missing' | 'repairable' | 'skipped';
|
|
@@ -40,6 +44,35 @@ export interface AgentGuidanceCheck {
|
|
|
40
44
|
issues: string[];
|
|
41
45
|
}
|
|
42
46
|
|
|
47
|
+
export interface AgentPluginCheck {
|
|
48
|
+
scope: 'global';
|
|
49
|
+
kind: 'bundle' | 'marketplace' | 'runtime' | 'hook-trust';
|
|
50
|
+
path: string;
|
|
51
|
+
exists: boolean;
|
|
52
|
+
status: AgentIntegrationStatus;
|
|
53
|
+
issues: string[];
|
|
54
|
+
version?: string;
|
|
55
|
+
hooks?: {
|
|
56
|
+
declared: string[];
|
|
57
|
+
expected: string[];
|
|
58
|
+
};
|
|
59
|
+
runtime?: {
|
|
60
|
+
installed: boolean;
|
|
61
|
+
enabled: boolean;
|
|
62
|
+
version?: string;
|
|
63
|
+
};
|
|
64
|
+
hookTrust?: {
|
|
65
|
+
trusted: string[];
|
|
66
|
+
expected: string[];
|
|
67
|
+
};
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
export interface AgentPluginStatus {
|
|
71
|
+
status: AgentIntegrationStatus;
|
|
72
|
+
issues: string[];
|
|
73
|
+
checks: AgentPluginCheck[];
|
|
74
|
+
}
|
|
75
|
+
|
|
43
76
|
export interface AgentIntegrationEntry {
|
|
44
77
|
agent: AgentName;
|
|
45
78
|
mcp: {
|
|
@@ -52,6 +85,7 @@ export interface AgentIntegrationEntry {
|
|
|
52
85
|
issues: string[];
|
|
53
86
|
checks: AgentGuidanceCheck[];
|
|
54
87
|
};
|
|
88
|
+
plugin: AgentPluginStatus;
|
|
55
89
|
}
|
|
56
90
|
|
|
57
91
|
export interface AgentIntegrationReport {
|
|
@@ -89,6 +123,12 @@ const GUIDANCE_AGENTS = new Set<AgentName>([
|
|
|
89
123
|
'trae',
|
|
90
124
|
]);
|
|
91
125
|
|
|
126
|
+
const CODEX_PLUGIN_NAME = 'memorix';
|
|
127
|
+
const CODEX_PLUGIN_MARKETPLACE = 'personal';
|
|
128
|
+
const CODEX_PLUGIN_ID = `${CODEX_PLUGIN_NAME}@${CODEX_PLUGIN_MARKETPLACE}`;
|
|
129
|
+
const CODEX_HOOK_EVENTS = ['SessionStart', 'UserPromptSubmit', 'PostToolUse', 'PreCompact', 'Stop'];
|
|
130
|
+
const CODEX_HOOK_STATE_NAMES = ['session_start', 'user_prompt_submit', 'post_tool_use', 'pre_compact', 'stop'];
|
|
131
|
+
|
|
92
132
|
type ConcreteAgentIntegrationScope = Exclude<AgentIntegrationScope, 'all'>;
|
|
93
133
|
type JsonRecord = Record<string, unknown>;
|
|
94
134
|
|
|
@@ -179,6 +219,310 @@ function asRecord(value: unknown): JsonRecord | null {
|
|
|
179
219
|
return value && typeof value === 'object' && !Array.isArray(value) ? value as JsonRecord : null;
|
|
180
220
|
}
|
|
181
221
|
|
|
222
|
+
function asRecordArray(value: unknown): JsonRecord[] {
|
|
223
|
+
return Array.isArray(value)
|
|
224
|
+
? value.map(asRecord).filter((entry): entry is JsonRecord => entry !== null)
|
|
225
|
+
: [];
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
function codexPluginPath(): string {
|
|
229
|
+
return `${homedir()}/.codex/plugins/${CODEX_PLUGIN_NAME}`;
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
function codexMarketplacePath(): string {
|
|
233
|
+
return `${homedir()}/.agents/plugins/marketplace.json`;
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
function codexConfigPath(): string {
|
|
237
|
+
return `${homedir()}/.codex/config.toml`;
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
function normalizeMarketplacePath(value: string): string {
|
|
241
|
+
return value.replace(/\\/g, '/').replace(/^\.\//, '');
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
function isMemorixCodexPlugin(value: JsonRecord): boolean {
|
|
245
|
+
return value.pluginId === CODEX_PLUGIN_ID
|
|
246
|
+
|| (value.name === CODEX_PLUGIN_NAME && value.marketplaceName === CODEX_PLUGIN_MARKETPLACE);
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
export function parseCodexPluginList(output: string): AgentPluginCheck['runtime'] | null {
|
|
250
|
+
try {
|
|
251
|
+
const parsed = asRecord(JSON.parse(output));
|
|
252
|
+
if (!parsed) return null;
|
|
253
|
+
const entries = [...asRecordArray(parsed.installed), ...asRecordArray(parsed.available)];
|
|
254
|
+
const plugin = entries.find(isMemorixCodexPlugin);
|
|
255
|
+
if (!plugin) return { installed: false, enabled: false };
|
|
256
|
+
return {
|
|
257
|
+
installed: plugin.installed === true,
|
|
258
|
+
enabled: plugin.enabled === true,
|
|
259
|
+
...(typeof plugin.version === 'string' ? { version: plugin.version } : {}),
|
|
260
|
+
};
|
|
261
|
+
} catch {
|
|
262
|
+
return null;
|
|
263
|
+
}
|
|
264
|
+
}
|
|
265
|
+
|
|
266
|
+
async function inspectCodexPluginBundle(): Promise<AgentPluginCheck> {
|
|
267
|
+
const pluginPath = codexPluginPath();
|
|
268
|
+
const manifestPath = `${pluginPath}/.codex-plugin/plugin.json`;
|
|
269
|
+
const hooksPath = `${pluginPath}/hooks/hooks.json`;
|
|
270
|
+
if (!existsSync(pluginPath)) {
|
|
271
|
+
return {
|
|
272
|
+
scope: 'global',
|
|
273
|
+
kind: 'bundle',
|
|
274
|
+
path: pluginPath,
|
|
275
|
+
exists: false,
|
|
276
|
+
status: 'missing',
|
|
277
|
+
issues: ['codex-plugin-bundle-missing'],
|
|
278
|
+
};
|
|
279
|
+
}
|
|
280
|
+
if (!existsSync(manifestPath)) {
|
|
281
|
+
return {
|
|
282
|
+
scope: 'global',
|
|
283
|
+
kind: 'bundle',
|
|
284
|
+
path: manifestPath,
|
|
285
|
+
exists: false,
|
|
286
|
+
status: 'repairable',
|
|
287
|
+
issues: ['codex-plugin-manifest-missing'],
|
|
288
|
+
};
|
|
289
|
+
}
|
|
290
|
+
|
|
291
|
+
let manifest: JsonRecord | null = null;
|
|
292
|
+
try {
|
|
293
|
+
manifest = asRecord(JSON.parse(await readFile(manifestPath, 'utf-8')));
|
|
294
|
+
} catch {
|
|
295
|
+
return {
|
|
296
|
+
scope: 'global',
|
|
297
|
+
kind: 'bundle',
|
|
298
|
+
path: manifestPath,
|
|
299
|
+
exists: true,
|
|
300
|
+
status: 'repairable',
|
|
301
|
+
issues: ['codex-plugin-manifest-unreadable'],
|
|
302
|
+
};
|
|
303
|
+
}
|
|
304
|
+
if (!manifest || manifest.name !== CODEX_PLUGIN_NAME) {
|
|
305
|
+
return {
|
|
306
|
+
scope: 'global',
|
|
307
|
+
kind: 'bundle',
|
|
308
|
+
path: manifestPath,
|
|
309
|
+
exists: true,
|
|
310
|
+
status: 'repairable',
|
|
311
|
+
issues: ['codex-plugin-manifest-invalid'],
|
|
312
|
+
};
|
|
313
|
+
}
|
|
314
|
+
|
|
315
|
+
const version = typeof manifest.version === 'string' ? manifest.version : undefined;
|
|
316
|
+
const issues: string[] = [];
|
|
317
|
+
if (version !== getCliVersion()) issues.push('codex-plugin-version-mismatch');
|
|
318
|
+
if (manifest.hooks !== './hooks/hooks.json') issues.push('codex-hook-manifest-missing');
|
|
319
|
+
|
|
320
|
+
let declared: string[] = [];
|
|
321
|
+
if (!existsSync(hooksPath)) {
|
|
322
|
+
issues.push('codex-hook-manifest-missing');
|
|
323
|
+
} else {
|
|
324
|
+
try {
|
|
325
|
+
const hooksConfig = asRecord(JSON.parse(await readFile(hooksPath, 'utf-8')));
|
|
326
|
+
const hooks = asRecord(hooksConfig?.hooks);
|
|
327
|
+
declared = hooks ? Object.keys(hooks) : [];
|
|
328
|
+
if (CODEX_HOOK_EVENTS.some((event) => !declared.includes(event))) {
|
|
329
|
+
issues.push('codex-hook-events-missing');
|
|
330
|
+
}
|
|
331
|
+
} catch {
|
|
332
|
+
issues.push('codex-hook-manifest-unreadable');
|
|
333
|
+
}
|
|
334
|
+
}
|
|
335
|
+
|
|
336
|
+
return {
|
|
337
|
+
scope: 'global',
|
|
338
|
+
kind: 'bundle',
|
|
339
|
+
path: pluginPath,
|
|
340
|
+
exists: true,
|
|
341
|
+
status: issues.length > 0 ? 'repairable' : 'ok',
|
|
342
|
+
issues: unique(issues),
|
|
343
|
+
...(version ? { version } : {}),
|
|
344
|
+
hooks: { declared, expected: [...CODEX_HOOK_EVENTS] },
|
|
345
|
+
};
|
|
346
|
+
}
|
|
347
|
+
|
|
348
|
+
async function inspectCodexMarketplace(): Promise<AgentPluginCheck> {
|
|
349
|
+
const marketplacePath = codexMarketplacePath();
|
|
350
|
+
if (!existsSync(marketplacePath)) {
|
|
351
|
+
return {
|
|
352
|
+
scope: 'global',
|
|
353
|
+
kind: 'marketplace',
|
|
354
|
+
path: marketplacePath,
|
|
355
|
+
exists: false,
|
|
356
|
+
status: 'missing',
|
|
357
|
+
issues: ['codex-marketplace-missing'],
|
|
358
|
+
};
|
|
359
|
+
}
|
|
360
|
+
|
|
361
|
+
let catalog: JsonRecord | null = null;
|
|
362
|
+
try {
|
|
363
|
+
catalog = asRecord(JSON.parse(await readFile(marketplacePath, 'utf-8')));
|
|
364
|
+
} catch {
|
|
365
|
+
return {
|
|
366
|
+
scope: 'global',
|
|
367
|
+
kind: 'marketplace',
|
|
368
|
+
path: marketplacePath,
|
|
369
|
+
exists: true,
|
|
370
|
+
status: 'repairable',
|
|
371
|
+
issues: ['codex-marketplace-unreadable'],
|
|
372
|
+
};
|
|
373
|
+
}
|
|
374
|
+
|
|
375
|
+
const entry = asRecordArray(catalog?.plugins).find((plugin) => plugin.name === CODEX_PLUGIN_NAME);
|
|
376
|
+
if (!entry) {
|
|
377
|
+
return {
|
|
378
|
+
scope: 'global',
|
|
379
|
+
kind: 'marketplace',
|
|
380
|
+
path: marketplacePath,
|
|
381
|
+
exists: true,
|
|
382
|
+
status: 'repairable',
|
|
383
|
+
issues: ['codex-marketplace-entry-missing'],
|
|
384
|
+
};
|
|
385
|
+
}
|
|
386
|
+
|
|
387
|
+
const source = asRecord(entry.source);
|
|
388
|
+
const sourcePath = typeof source?.path === 'string' ? source.path : '';
|
|
389
|
+
const issues = source?.source === 'local' && normalizeMarketplacePath(sourcePath) === '.codex/plugins/memorix'
|
|
390
|
+
? []
|
|
391
|
+
: ['codex-marketplace-entry-stale'];
|
|
392
|
+
return {
|
|
393
|
+
scope: 'global',
|
|
394
|
+
kind: 'marketplace',
|
|
395
|
+
path: marketplacePath,
|
|
396
|
+
exists: true,
|
|
397
|
+
status: issues.length > 0 ? 'repairable' : 'ok',
|
|
398
|
+
issues,
|
|
399
|
+
};
|
|
400
|
+
}
|
|
401
|
+
|
|
402
|
+
function inspectCodexPluginRuntime(): AgentPluginCheck {
|
|
403
|
+
const command = 'codex plugin list --marketplace personal --available --json';
|
|
404
|
+
const result = spawnSync('codex', ['plugin', 'list', '--marketplace', CODEX_PLUGIN_MARKETPLACE, '--available', '--json'], {
|
|
405
|
+
encoding: 'utf-8',
|
|
406
|
+
stdio: 'pipe',
|
|
407
|
+
shell: process.platform === 'win32',
|
|
408
|
+
});
|
|
409
|
+
if (result.error || result.status !== 0) {
|
|
410
|
+
return {
|
|
411
|
+
scope: 'global',
|
|
412
|
+
kind: 'runtime',
|
|
413
|
+
path: command,
|
|
414
|
+
exists: false,
|
|
415
|
+
status: 'skipped',
|
|
416
|
+
issues: ['codex-plugin-runtime-unavailable'],
|
|
417
|
+
};
|
|
418
|
+
}
|
|
419
|
+
|
|
420
|
+
const runtime = parseCodexPluginList(String(result.stdout ?? ''));
|
|
421
|
+
if (!runtime) {
|
|
422
|
+
return {
|
|
423
|
+
scope: 'global',
|
|
424
|
+
kind: 'runtime',
|
|
425
|
+
path: command,
|
|
426
|
+
exists: true,
|
|
427
|
+
status: 'skipped',
|
|
428
|
+
issues: ['codex-plugin-runtime-unreadable'],
|
|
429
|
+
};
|
|
430
|
+
}
|
|
431
|
+
|
|
432
|
+
const issues: string[] = [];
|
|
433
|
+
if (!runtime.installed) issues.push('codex-plugin-not-installed');
|
|
434
|
+
else if (!runtime.enabled) issues.push('codex-plugin-disabled');
|
|
435
|
+
return {
|
|
436
|
+
scope: 'global',
|
|
437
|
+
kind: 'runtime',
|
|
438
|
+
path: command,
|
|
439
|
+
exists: true,
|
|
440
|
+
status: issues.length > 0 ? 'repairable' : 'ok',
|
|
441
|
+
issues,
|
|
442
|
+
runtime,
|
|
443
|
+
};
|
|
444
|
+
}
|
|
445
|
+
|
|
446
|
+
async function inspectCodexHookTrust(): Promise<AgentPluginCheck> {
|
|
447
|
+
const configPath = codexConfigPath();
|
|
448
|
+
if (!existsSync(configPath)) {
|
|
449
|
+
return {
|
|
450
|
+
scope: 'global',
|
|
451
|
+
kind: 'hook-trust',
|
|
452
|
+
path: configPath,
|
|
453
|
+
exists: false,
|
|
454
|
+
status: 'skipped',
|
|
455
|
+
issues: ['codex-hook-trust-unavailable'],
|
|
456
|
+
};
|
|
457
|
+
}
|
|
458
|
+
|
|
459
|
+
let config = '';
|
|
460
|
+
try {
|
|
461
|
+
config = await readFile(configPath, 'utf-8');
|
|
462
|
+
} catch {
|
|
463
|
+
return {
|
|
464
|
+
scope: 'global',
|
|
465
|
+
kind: 'hook-trust',
|
|
466
|
+
path: configPath,
|
|
467
|
+
exists: true,
|
|
468
|
+
status: 'skipped',
|
|
469
|
+
issues: ['codex-hook-trust-unreadable'],
|
|
470
|
+
};
|
|
471
|
+
}
|
|
472
|
+
|
|
473
|
+
const trusted = CODEX_HOOK_STATE_NAMES.filter((event) =>
|
|
474
|
+
config.includes(`[hooks.state."${CODEX_PLUGIN_ID}:hooks/hooks.json:${event}:`)
|
|
475
|
+
);
|
|
476
|
+
const issues = trusted.length === CODEX_HOOK_STATE_NAMES.length ? [] : ['codex-hook-trust-pending'];
|
|
477
|
+
return {
|
|
478
|
+
scope: 'global',
|
|
479
|
+
kind: 'hook-trust',
|
|
480
|
+
path: configPath,
|
|
481
|
+
exists: true,
|
|
482
|
+
status: issues.length > 0 ? 'repairable' : 'ok',
|
|
483
|
+
issues,
|
|
484
|
+
hookTrust: { trusted, expected: [...CODEX_HOOK_STATE_NAMES] },
|
|
485
|
+
};
|
|
486
|
+
}
|
|
487
|
+
|
|
488
|
+
function aggregatePluginStatus(checks: AgentPluginCheck[]): AgentIntegrationStatus {
|
|
489
|
+
if (checks.length === 0) return 'skipped';
|
|
490
|
+
return worstStatus(checks.map((check) => check.status));
|
|
491
|
+
}
|
|
492
|
+
|
|
493
|
+
function aggregatePluginIssues(checks: AgentPluginCheck[]): string[] {
|
|
494
|
+
const status = aggregatePluginStatus(checks);
|
|
495
|
+
if (status === 'ok') {
|
|
496
|
+
return unique(checks.filter((check) => check.status === 'skipped').flatMap((check) => check.issues));
|
|
497
|
+
}
|
|
498
|
+
if (status === 'repairable') {
|
|
499
|
+
return unique(checks
|
|
500
|
+
.filter((check) => check.status === 'repairable' || check.status === 'missing')
|
|
501
|
+
.flatMap((check) => check.issues));
|
|
502
|
+
}
|
|
503
|
+
return unique(checks
|
|
504
|
+
.filter((check) => check.status === status)
|
|
505
|
+
.flatMap((check) => check.issues));
|
|
506
|
+
}
|
|
507
|
+
|
|
508
|
+
async function inspectPlugin(agent: AgentName, scope: AgentIntegrationScope): Promise<AgentPluginStatus> {
|
|
509
|
+
if (agent !== 'codex' || scope === 'local' || scope === 'project') {
|
|
510
|
+
return { status: 'skipped', issues: [], checks: [] };
|
|
511
|
+
}
|
|
512
|
+
|
|
513
|
+
const checks = [
|
|
514
|
+
await inspectCodexPluginBundle(),
|
|
515
|
+
await inspectCodexMarketplace(),
|
|
516
|
+
inspectCodexPluginRuntime(),
|
|
517
|
+
await inspectCodexHookTrust(),
|
|
518
|
+
];
|
|
519
|
+
return {
|
|
520
|
+
status: aggregatePluginStatus(checks),
|
|
521
|
+
issues: aggregatePluginIssues(checks),
|
|
522
|
+
checks,
|
|
523
|
+
};
|
|
524
|
+
}
|
|
525
|
+
|
|
182
526
|
function looksLikeStaleMemorixCommand(server: MCPServerEntry): boolean {
|
|
183
527
|
const text = [server.command, ...(server.args ?? [])].join(' ');
|
|
184
528
|
return /[\\/]\.worktrees[\\/]/i.test(text) || /[\\/]dist[\\/]cli[\\/]index\.js/i.test(text);
|
|
@@ -492,10 +836,11 @@ export async function inspectAgentIntegrations(options: {
|
|
|
492
836
|
agent,
|
|
493
837
|
mcp: await inspectMcp(agent, projectRoot, scope),
|
|
494
838
|
guidance: await inspectGuidance(agent, projectRoot, scope),
|
|
839
|
+
plugin: await inspectPlugin(agent, scope),
|
|
495
840
|
});
|
|
496
841
|
}
|
|
497
842
|
|
|
498
|
-
const entryStatuses = entries.map((entry) => worstStatus([entry.mcp.status, entry.guidance.status]));
|
|
843
|
+
const entryStatuses = entries.map((entry) => worstStatus([entry.mcp.status, entry.guidance.status, entry.plugin.status]));
|
|
499
844
|
return {
|
|
500
845
|
projectRoot,
|
|
501
846
|
scope,
|
|
@@ -519,10 +864,11 @@ export function formatAgentIntegrationReport(report: AgentIntegrationReport): st
|
|
|
519
864
|
];
|
|
520
865
|
|
|
521
866
|
for (const entry of report.entries) {
|
|
522
|
-
const status = worstStatus([entry.mcp.status, entry.guidance.status]);
|
|
867
|
+
const status = worstStatus([entry.mcp.status, entry.guidance.status, entry.plugin.status]);
|
|
523
868
|
lines.push(`${entry.agent}: ${status}`);
|
|
524
869
|
if (entry.mcp.issues.length > 0) lines.push(` MCP: ${entry.mcp.issues.join(', ')}`);
|
|
525
870
|
if (entry.guidance.issues.length > 0) lines.push(` Guidance: ${entry.guidance.issues.join(', ')}`);
|
|
871
|
+
if (entry.plugin.issues.length > 0) lines.push(` Plugin: ${entry.plugin.issues.join(', ')}`);
|
|
526
872
|
}
|
|
527
873
|
|
|
528
874
|
lines.push('');
|
|
@@ -586,6 +932,42 @@ export async function repairAgentIntegrations(options: {
|
|
|
586
932
|
} else {
|
|
587
933
|
skipped.push(`${entry.agent}:guidance`);
|
|
588
934
|
}
|
|
935
|
+
|
|
936
|
+
if (entry.agent === 'codex' && entry.plugin.status !== 'ok' && entry.plugin.status !== 'skipped') {
|
|
937
|
+
const bundleOrMarketplaceNeedsRepair = entry.plugin.checks.some((check) =>
|
|
938
|
+
(check.kind === 'bundle' || check.kind === 'marketplace') && check.status !== 'ok'
|
|
939
|
+
);
|
|
940
|
+
const runtime = entry.plugin.checks.find((check) => check.kind === 'runtime');
|
|
941
|
+
const hookTrust = entry.plugin.checks.find((check) => check.kind === 'hook-trust');
|
|
942
|
+
const needsInstall = runtime?.runtime?.installed === false;
|
|
943
|
+
const needsManualEnable = runtime?.runtime?.installed === true && runtime.runtime.enabled === false;
|
|
944
|
+
const needsHookTrust = hookTrust?.status === 'repairable';
|
|
945
|
+
const missingPluginFiles = entry.plugin.checks.some((check) =>
|
|
946
|
+
(check.kind === 'bundle' || check.kind === 'marketplace') && check.status === 'missing'
|
|
947
|
+
);
|
|
948
|
+
|
|
949
|
+
if (needsManualEnable) {
|
|
950
|
+
skipped.push('codex:plugin:global:enable-in-plugin-browser');
|
|
951
|
+
}
|
|
952
|
+
if (needsHookTrust) {
|
|
953
|
+
skipped.push('codex:plugin:global:review-hooks');
|
|
954
|
+
}
|
|
955
|
+
|
|
956
|
+
if (bundleOrMarketplaceNeedsRepair || needsInstall) {
|
|
957
|
+
if ((missingPluginFiles || needsInstall) && !canInstallMissing) {
|
|
958
|
+
skipped.push('codex:plugin:global:missing');
|
|
959
|
+
} else {
|
|
960
|
+
if (!options.dry) {
|
|
961
|
+
await installPluginPackage({ agent: 'codex' });
|
|
962
|
+
if (needsInstall) {
|
|
963
|
+
const install = tryInstallCodexPlugin();
|
|
964
|
+
if (!install.ok) skipped.push('codex:plugin:global:install-pending');
|
|
965
|
+
}
|
|
966
|
+
}
|
|
967
|
+
changed.push('codex:plugin:global');
|
|
968
|
+
}
|
|
969
|
+
}
|
|
970
|
+
}
|
|
589
971
|
}
|
|
590
972
|
|
|
591
973
|
return {
|
|
@@ -1,20 +1,54 @@
|
|
|
1
1
|
import { defineCommand } from 'citty';
|
|
2
2
|
import { CodeGraphStore } from '../../codegraph/store.js';
|
|
3
3
|
import { refreshProjectLite } from '../../codegraph/lite-provider.js';
|
|
4
|
-
import { assembleContextPackForTask, buildContextPackPrompt } from '../../codegraph/context-pack.js';
|
|
4
|
+
import { assembleContextPackForTask, attachTaskWorkset, buildContextPackPrompt } from '../../codegraph/context-pack.js';
|
|
5
5
|
import { backfillMissingObservationCodeRefs } from '../../codegraph/binder.js';
|
|
6
|
+
import { collectCurrentProjectFacts } from '../../codegraph/current-facts.js';
|
|
7
|
+
import { resolveTaskLens } from '../../codegraph/task-lens.js';
|
|
8
|
+
import { getExternalCodeGraphContext, inspectExternalCodeGraph } from '../../codegraph/external-provider.js';
|
|
9
|
+
import type { CodeGraphProviderQuality } from '../../codegraph/types.js';
|
|
6
10
|
import { getResolvedConfig } from '../../config/resolved-config.js';
|
|
7
11
|
import { getAllObservations } from '../../memory/observations.js';
|
|
8
12
|
import { emitError, emitResult, getCliProjectContext, parsePositiveInt } from './operator-shared.js';
|
|
9
13
|
|
|
10
|
-
function
|
|
14
|
+
function formatSnapshotStatus(status: ReturnType<CodeGraphStore['status']>): string[] {
|
|
15
|
+
const snapshot = status.latestSnapshot;
|
|
16
|
+
if (!snapshot) return ['- Code state: no completed snapshot yet'];
|
|
17
|
+
const revision = snapshot.baseRevision ? snapshot.baseRevision.slice(0, 12) : 'Git unavailable';
|
|
18
|
+
const completeness = snapshot.completeness;
|
|
19
|
+
const scanState = completeness.skippedOversizedFiles > 0
|
|
20
|
+
|| (completeness.unreadableFiles ?? 0) > 0
|
|
21
|
+
|| completeness.removalScanDeferred
|
|
22
|
+
? 'incomplete'
|
|
23
|
+
: 'complete';
|
|
11
24
|
return [
|
|
25
|
+
'- Code state: ' + revision
|
|
26
|
+
+ ', ' + snapshot.worktreeState + ' worktree'
|
|
27
|
+
+ ', ' + snapshot.changedPathCount + ' changed path(s)'
|
|
28
|
+
+ ', epoch ' + snapshot.sourceEpoch,
|
|
29
|
+
'- Scan completeness: ' + scanState
|
|
30
|
+
+ ' (' + completeness.scannedFiles + '/' + completeness.maxFiles + ' paths'
|
|
31
|
+
+ ', ' + completeness.skippedOversizedFiles + ' oversized skipped'
|
|
32
|
+
+ ', ' + (completeness.unreadableFiles ?? 0) + ' unreadable)',
|
|
33
|
+
];
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
function formatStatus(status: ReturnType<CodeGraphStore['status']>, quality?: CodeGraphProviderQuality): string {
|
|
37
|
+
return [
|
|
38
|
+
...formatSnapshotStatus(status),
|
|
12
39
|
`CodeGraph Memory: ${status.provider}`,
|
|
13
40
|
`- Files: ${status.files}`,
|
|
14
41
|
`- Symbols: ${status.symbols}`,
|
|
15
42
|
`- Edges: ${status.edges}`,
|
|
16
43
|
`- Memory refs: ${status.refs}`,
|
|
17
44
|
status.indexedAt ? `- Indexed at: ${status.indexedAt}` : '- Indexed at: never',
|
|
45
|
+
...(quality
|
|
46
|
+
? [
|
|
47
|
+
`- Persistent provider: ${status.provider} (heuristic local index)`,
|
|
48
|
+
`- External semantic CodeGraph: ${quality.external.state}`
|
|
49
|
+
+ (quality.external.reason ? ` (${quality.external.reason})` : ''),
|
|
50
|
+
]
|
|
51
|
+
: []),
|
|
18
52
|
].join('\n');
|
|
19
53
|
}
|
|
20
54
|
|
|
@@ -29,6 +63,19 @@ function formatUsageHint(): string {
|
|
|
29
63
|
].join('\n');
|
|
30
64
|
}
|
|
31
65
|
|
|
66
|
+
function compactFacts(project: { rootPath: string }): { facts: string[]; dirty: boolean } {
|
|
67
|
+
const current = collectCurrentProjectFacts({ project, now: new Date() });
|
|
68
|
+
const facts: string[] = [];
|
|
69
|
+
if (current.packageVersion) facts.push('Package version: ' + current.packageVersion);
|
|
70
|
+
if (current.latestChangelog) {
|
|
71
|
+
facts.push('Latest changelog: ' + current.latestChangelog.version
|
|
72
|
+
+ (current.latestChangelog.date ? ' (' + current.latestChangelog.date + ')' : ''));
|
|
73
|
+
}
|
|
74
|
+
facts.push('Git: ' + (current.git.branch ? 'branch ' + current.git.branch + ', ' : '')
|
|
75
|
+
+ (current.git.dirty ? 'dirty worktree' : 'clean worktree'));
|
|
76
|
+
return { facts, dirty: current.git.dirty };
|
|
77
|
+
}
|
|
78
|
+
|
|
32
79
|
export default defineCommand({
|
|
33
80
|
meta: {
|
|
34
81
|
name: 'codegraph',
|
|
@@ -56,10 +103,16 @@ export default defineCommand({
|
|
|
56
103
|
switch (action) {
|
|
57
104
|
case 'status': {
|
|
58
105
|
const status = store.status(project.id);
|
|
106
|
+
const providerQuality = await inspectExternalCodeGraph({
|
|
107
|
+
projectRoot: project.rootPath,
|
|
108
|
+
mode: codegraphConfig.externalContext,
|
|
109
|
+
command: codegraphConfig.externalCommand,
|
|
110
|
+
timeoutMs: codegraphConfig.externalTimeoutMs,
|
|
111
|
+
});
|
|
59
112
|
const text = explicitAction || asJson
|
|
60
|
-
? formatStatus(status)
|
|
61
|
-
: `${formatStatus(status)}\n\n${formatUsageHint()}`;
|
|
62
|
-
emitResult({ project, status }, text, asJson);
|
|
113
|
+
? formatStatus(status, providerQuality.quality)
|
|
114
|
+
: `${formatStatus(status, providerQuality.quality)}\n\n${formatUsageHint()}`;
|
|
115
|
+
emitResult({ project, status, providerQuality: providerQuality.quality }, text, asJson);
|
|
63
116
|
return;
|
|
64
117
|
}
|
|
65
118
|
|
|
@@ -73,12 +126,25 @@ export default defineCommand({
|
|
|
73
126
|
const activeObservations = getAllObservations()
|
|
74
127
|
.filter(obs => obs.projectId === project.id && (obs.status ?? 'active') === 'active');
|
|
75
128
|
const backfill = await backfillMissingObservationCodeRefs(store, activeObservations);
|
|
129
|
+
const { enqueueClaimRequalification } = await import('../../runtime/lifecycle.js');
|
|
130
|
+
enqueueClaimRequalification({
|
|
131
|
+
dataDir,
|
|
132
|
+
projectId: project.id,
|
|
133
|
+
source: 'manual-codegraph-refresh',
|
|
134
|
+
snapshotId: refresh.snapshot.id,
|
|
135
|
+
});
|
|
76
136
|
const status = store.status(project.id);
|
|
137
|
+
const providerQuality = await inspectExternalCodeGraph({
|
|
138
|
+
projectRoot: project.rootPath,
|
|
139
|
+
mode: codegraphConfig.externalContext,
|
|
140
|
+
command: codegraphConfig.externalCommand,
|
|
141
|
+
timeoutMs: codegraphConfig.externalTimeoutMs,
|
|
142
|
+
});
|
|
77
143
|
emitResult(
|
|
78
|
-
{ project, status, refresh, backfill },
|
|
144
|
+
{ project, status, providerQuality: providerQuality.quality, refresh, backfill },
|
|
79
145
|
[
|
|
80
146
|
'CodeGraph Memory refreshed.',
|
|
81
|
-
formatStatus(status),
|
|
147
|
+
formatStatus(status, providerQuality.quality),
|
|
82
148
|
`- Files: ${refresh.changedFiles} changed, ${refresh.unchangedFiles} unchanged, ${refresh.removedFiles} removed`,
|
|
83
149
|
`- Backfilled memories: ${backfill.observationsBackfilled}`,
|
|
84
150
|
`- Backfilled refs: ${backfill.refsBackfilled}`,
|
|
@@ -98,7 +164,7 @@ export default defineCommand({
|
|
|
98
164
|
const observations = getAllObservations()
|
|
99
165
|
.filter(obs => obs.projectId === project.id && (obs.status ?? 'active') === 'active')
|
|
100
166
|
.reverse();
|
|
101
|
-
const
|
|
167
|
+
const basePack = assembleContextPackForTask({
|
|
102
168
|
store,
|
|
103
169
|
projectId: project.id,
|
|
104
170
|
task,
|
|
@@ -106,7 +172,44 @@ export default defineCommand({
|
|
|
106
172
|
limit,
|
|
107
173
|
exclude,
|
|
108
174
|
});
|
|
109
|
-
|
|
175
|
+
const status = store.status(project.id);
|
|
176
|
+
const facts = compactFacts(project);
|
|
177
|
+
const snapshot = status.latestSnapshot;
|
|
178
|
+
const external = await getExternalCodeGraphContext({
|
|
179
|
+
projectRoot: project.rootPath,
|
|
180
|
+
task,
|
|
181
|
+
exclude,
|
|
182
|
+
mode: codegraphConfig.externalContext,
|
|
183
|
+
command: codegraphConfig.externalCommand,
|
|
184
|
+
timeoutMs: codegraphConfig.externalTimeoutMs,
|
|
185
|
+
});
|
|
186
|
+
const pack = await attachTaskWorkset({
|
|
187
|
+
pack: basePack,
|
|
188
|
+
projectId: project.id,
|
|
189
|
+
dataDir,
|
|
190
|
+
lens: resolveTaskLens(task).id,
|
|
191
|
+
worktreeDirty: facts.dirty,
|
|
192
|
+
currentFacts: facts.facts,
|
|
193
|
+
codeState: formatSnapshotStatus(status).join(' '),
|
|
194
|
+
...(snapshot
|
|
195
|
+
? {
|
|
196
|
+
snapshot: {
|
|
197
|
+
id: snapshot.id,
|
|
198
|
+
sourceEpoch: snapshot.sourceEpoch,
|
|
199
|
+
worktreeState: snapshot.worktreeState,
|
|
200
|
+
incomplete: snapshot.completeness.skippedOversizedFiles > 0
|
|
201
|
+
|| (snapshot.completeness.unreadableFiles ?? 0) > 0
|
|
202
|
+
|| snapshot.completeness.removalScanDeferred,
|
|
203
|
+
},
|
|
204
|
+
}
|
|
205
|
+
: {}),
|
|
206
|
+
...(external.outline ? { semanticCode: external.outline } : {}),
|
|
207
|
+
providerQuality: external.quality,
|
|
208
|
+
...(external.caution
|
|
209
|
+
? { runtimeCautions: [{ kind: 'external-codegraph-fallback' as const, message: external.caution }] }
|
|
210
|
+
: {}),
|
|
211
|
+
});
|
|
212
|
+
emitResult({ project, pack, providerQuality: external.quality }, buildContextPackPrompt(pack), asJson);
|
|
110
213
|
return;
|
|
111
214
|
}
|
|
112
215
|
|
|
@@ -45,6 +45,8 @@ export default defineCommand({
|
|
|
45
45
|
currentFacts: context.currentFacts,
|
|
46
46
|
overview: context.overview,
|
|
47
47
|
refresh: context.refresh,
|
|
48
|
+
providerQuality: context.providerQuality,
|
|
49
|
+
workset: context.workset,
|
|
48
50
|
...(context.task ? { task: context.task } : {}),
|
|
49
51
|
},
|
|
50
52
|
formatAutoProjectContextPrompt(context),
|