metame-cli 1.5.4 → 1.5.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/README.md +6 -1
- package/index.js +277 -55
- package/package.json +3 -2
- package/scripts/agent-layer.js +4 -2
- package/scripts/bin/dispatch_to +18 -6
- package/scripts/bin/push-clean.sh +72 -0
- package/scripts/daemon-admin-commands.js +266 -64
- package/scripts/daemon-agent-commands.js +188 -66
- package/scripts/daemon-bridges.js +475 -50
- package/scripts/daemon-checkpoints.js +84 -30
- package/scripts/daemon-claude-engine.js +651 -103
- package/scripts/daemon-command-router.js +134 -27
- package/scripts/daemon-command-session-route.js +118 -0
- package/scripts/daemon-default.yaml +2 -0
- package/scripts/daemon-dispatch-cards.js +185 -0
- package/scripts/daemon-engine-runtime.js +96 -20
- package/scripts/daemon-exec-commands.js +106 -50
- package/scripts/daemon-file-browser.js +63 -7
- package/scripts/daemon-notify.js +18 -4
- package/scripts/daemon-ops-commands.js +28 -6
- package/scripts/daemon-remote-dispatch.js +34 -2
- package/scripts/daemon-session-commands.js +102 -45
- package/scripts/daemon-session-store.js +497 -66
- package/scripts/daemon-siri-bridge.js +234 -0
- package/scripts/daemon-siri-imessage.js +209 -0
- package/scripts/daemon-task-scheduler.js +10 -2
- package/scripts/{team-dispatch.js → daemon-team-dispatch.js} +150 -11
- package/scripts/daemon.js +484 -181
- package/scripts/docs/hook-config.md +7 -4
- package/scripts/docs/maintenance-manual.md +10 -3
- package/scripts/docs/pointer-map.md +2 -2
- package/scripts/feishu-adapter.js +7 -15
- package/scripts/hooks/doc-router.js +29 -0
- package/scripts/hooks/intent-doc-router.js +54 -0
- package/scripts/hooks/intent-engine.js +9 -40
- package/scripts/intent-registry.js +59 -0
- package/scripts/memory-extract.js +59 -0
- package/scripts/mentor-engine.js +6 -0
- package/scripts/schema.js +1 -0
- package/scripts/self-reflect.js +110 -12
- package/scripts/session-analytics.js +160 -0
- package/scripts/signal-capture.js +1 -1
- package/scripts/hooks/intent-agent-manage.js +0 -50
- package/scripts/hooks/intent-hook-config.js +0 -28
|
@@ -15,7 +15,8 @@ Stop (每轮结束)
|
|
|
15
15
|
└── stop-session-capture.js → session 事件日志 + 工具失败捕获
|
|
16
16
|
```
|
|
17
17
|
|
|
18
|
-
`intent-
|
|
18
|
+
`scripts/intent-registry.js` 是单一维护源,负责调用各意图模块并返回提示块。
|
|
19
|
+
`intent-engine.js` 是 Claude hook adapter;daemon 里的 Codex 路径也复用同一 registry。
|
|
19
20
|
零匹配 → 零输出(不浪费 token)。
|
|
20
21
|
|
|
21
22
|
---
|
|
@@ -29,8 +30,7 @@ Stop (每轮结束)
|
|
|
29
30
|
| `task_create` | `intent-task-create.js` | 定时/提醒/每天X点 等调度语境 | `/task-add` 命令用法提示 |
|
|
30
31
|
| `file_transfer` | `intent-file-transfer.js` | "发给我/发过来/导出" 等文件传输语境 | `[[FILE:...]]` 协议 + 收发规则 |
|
|
31
32
|
| `memory_recall` | `intent-memory-recall.js` | "上次/之前/还记得" 等跨会话回忆语境 | `memory-search.js` 命令用法 |
|
|
32
|
-
| `
|
|
33
|
-
| `hook_config` | `intent-hook-config.js` | "hook/intent 配置/开关" 等引擎配置语境 | hook-config.md 文档指引 |
|
|
33
|
+
| `doc_router` | `intent-doc-router.js` | "创建/绑定 Agent"、"代码结构/脚本入口"、"hook/intent 配置" 等文档导向语境 | 统一 doc-router 文档指引 |
|
|
34
34
|
---
|
|
35
35
|
|
|
36
36
|
## 开关控制
|
|
@@ -85,6 +85,8 @@ const DEFAULTS = {
|
|
|
85
85
|
};
|
|
86
86
|
```
|
|
87
87
|
|
|
88
|
+
文档路由类场景优先复用 `scripts/hooks/doc-router.js`,只传 `patterns + title + docPath + summary`,不要再为每个文档问题单独建样板模块。
|
|
89
|
+
|
|
88
90
|
3. **在 daemon.yaml 加开关**(可选,默认开):
|
|
89
91
|
|
|
90
92
|
```yaml
|
|
@@ -124,7 +126,8 @@ for k, v in s.get('hooks', {}).items():
|
|
|
124
126
|
|
|
125
127
|
| 文件 | 说明 |
|
|
126
128
|
|------|------|
|
|
127
|
-
| `scripts/
|
|
129
|
+
| `scripts/intent-registry.js` | 共享意图注册表(Claude hook / Codex runtime 共用) |
|
|
130
|
+
| `scripts/hooks/intent-engine.js` | Claude hook adapter(源文件) |
|
|
128
131
|
| `~/.metame/hooks/intent-engine.js` | 部署副本(symlink) |
|
|
129
132
|
| `scripts/hooks/intent-*.js` | 各意图模块(源文件) |
|
|
130
133
|
| `~/.metame/daemon.yaml` | 用户配置(包含 `hooks:` 开关) |
|
|
@@ -147,7 +147,7 @@ feishu:
|
|
|
147
147
|
- ENGINE_MODEL_CONFIG(daemon-engine-runtime.js 集中管理)
|
|
148
148
|
- daemon-runtime-lifecycle.js 的语法检查和备份机制
|
|
149
149
|
- daemon-remote-dispatch.js(纯逻辑,无平台差异)
|
|
150
|
-
- team-dispatch.js(共享解析/hint/enrichment)
|
|
150
|
+
- daemon-team-dispatch.js(共享解析/hint/enrichment)
|
|
151
151
|
|
|
152
152
|
### 需分别维护(有平台/引擎特殊分支)
|
|
153
153
|
|
|
@@ -263,7 +263,7 @@ team 成员可以通过 `peer` 字段标记为"远端成员"——运行在另
|
|
|
263
263
|
|
|
264
264
|
### 配置
|
|
265
265
|
|
|
266
|
-
两台设备的 `daemon.yaml` 需要配置相同的 relay 群和共享密钥,不同的 `self`
|
|
266
|
+
两台设备的 `daemon.yaml` 需要配置相同的 relay 群和共享密钥,不同的 `self` 标识;但不能共用同一个飞书 bot。每台机器都必须使用自己独立的飞书应用 / bot 凭据。
|
|
267
267
|
|
|
268
268
|
```yaml
|
|
269
269
|
# Mac 端
|
|
@@ -283,6 +283,13 @@ feishu:
|
|
|
283
283
|
secret: shared-secret-key # 同一个密钥
|
|
284
284
|
```
|
|
285
285
|
|
|
286
|
+
注意:
|
|
287
|
+
|
|
288
|
+
- 可以共用同一个 relay 群。
|
|
289
|
+
- 可以共用同一个 `secret`。
|
|
290
|
+
- 不能共用同一个飞书 bot / `app_id` / `app_secret`。
|
|
291
|
+
- 原因是飞书对同一 bot 的事件投递可能随机落到任一在线客户端;而当前代码在收到 `to_peer !== self` 的 relay 包时会直接忽略,错误机器会把包吞掉。
|
|
292
|
+
|
|
286
293
|
team 成员添加 `peer` 字段指向远端设备:
|
|
287
294
|
|
|
288
295
|
```yaml
|
|
@@ -338,7 +345,7 @@ Claude 看到 hook 注入:
|
|
|
338
345
|
| `daemon-bridges.js` | Feishu bridge 拦截 relay 群消息 + `_dispatchToTeamMember` 远端分流 |
|
|
339
346
|
| `daemon-admin-commands.js` | `/dispatch peers` 查看配置 + `/dispatch to peer:project` 手动派发 |
|
|
340
347
|
| `scripts/bin/dispatch_to` | 支持 `peer:project` 格式 → 写 `remote-pending.jsonl` |
|
|
341
|
-
| `team-dispatch.js` | `buildTeamRosterHint()` 为远端成员生成 `peer:key` 格式命令 |
|
|
348
|
+
| `daemon-team-dispatch.js` | `buildTeamRosterHint()` 为远端成员生成 `peer:key` 格式命令 |
|
|
342
349
|
| `hooks/team-context.js` | intent hook 注入远端 `peer:key` dispatch 命令 |
|
|
343
350
|
|
|
344
351
|
### 管理命令
|
|
@@ -64,7 +64,7 @@
|
|
|
64
64
|
## 团队 Dispatch 与跨设备通信定位
|
|
65
65
|
|
|
66
66
|
- 共享 Dispatch 工具:
|
|
67
|
-
- `scripts/team-dispatch.js`
|
|
67
|
+
- `scripts/daemon-team-dispatch.js`
|
|
68
68
|
- 关键点:`resolveProjectKey()` 名称/昵称解析(含 team member `parent/member` 复合键);
|
|
69
69
|
`findTeamMember()` 文本前缀匹配团队成员昵称;
|
|
70
70
|
`buildTeamRosterHint()` 生成团队上下文块(远端成员自动带 `peer:key` 前缀);
|
|
@@ -156,7 +156,7 @@
|
|
|
156
156
|
1. 先看配置:`~/.metame/daemon.yaml` 与 `scripts/daemon-default.yaml`
|
|
157
157
|
2. 再看命令入口:`scripts/daemon-admin-commands.js`、`scripts/daemon-command-router.js`、`scripts/daemon-exec-commands.js`
|
|
158
158
|
3. 再看执行链路:`scripts/daemon-engine-runtime.js` → `scripts/daemon-claude-engine.js` → `scripts/mentor-engine.js`
|
|
159
|
-
4. 团队/跨设备:`scripts/team-dispatch.js` → `scripts/daemon-remote-dispatch.js` → `scripts/daemon-bridges.js`
|
|
159
|
+
4. 团队/跨设备:`scripts/daemon-team-dispatch.js` → `scripts/daemon-remote-dispatch.js` → `scripts/daemon-bridges.js`
|
|
160
160
|
5. 最后看离线任务:`scripts/distill.js`、`scripts/memory-extract.js`、`scripts/memory-nightly-reflect.js`
|
|
161
161
|
|
|
162
162
|
## 同步提示
|
|
@@ -298,16 +298,8 @@ function createBot(config) {
|
|
|
298
298
|
throw new Error(`File not found: ${filePath}`);
|
|
299
299
|
}
|
|
300
300
|
const fileName = path.basename(filePath);
|
|
301
|
-
const fileSize = fs.statSync(filePath).size;
|
|
302
|
-
|
|
303
|
-
// For text files under 4KB, just send as text
|
|
304
301
|
const ext = path.extname(filePath).toLowerCase();
|
|
305
302
|
const isText = ['.md', '.txt', '.json', '.yaml', '.yml', '.csv'].includes(ext);
|
|
306
|
-
if (isText && fileSize < 4096) {
|
|
307
|
-
const content = fs.readFileSync(filePath, 'utf8');
|
|
308
|
-
await this.sendMessage(chatId, `📄 ${fileName}:\n\`\`\`\n${content}\n\`\`\``);
|
|
309
|
-
return;
|
|
310
|
-
}
|
|
311
303
|
|
|
312
304
|
// For larger/binary files, try file upload
|
|
313
305
|
try {
|
|
@@ -332,7 +324,7 @@ function createBot(config) {
|
|
|
332
324
|
}
|
|
333
325
|
|
|
334
326
|
// 2. Send file message
|
|
335
|
-
await client.im.message.create({
|
|
327
|
+
const sendRes = await client.im.message.create({
|
|
336
328
|
params: { receive_id_type: 'chat_id' },
|
|
337
329
|
data: {
|
|
338
330
|
receive_id: chatId,
|
|
@@ -340,6 +332,9 @@ function createBot(config) {
|
|
|
340
332
|
content: JSON.stringify({ file_key: fileKey }),
|
|
341
333
|
},
|
|
342
334
|
});
|
|
335
|
+
const msgId = sendRes?.data?.message_id;
|
|
336
|
+
if (caption) await this.sendMessage(chatId, caption);
|
|
337
|
+
return msgId ? { message_id: msgId } : null;
|
|
343
338
|
} catch (uploadErr) {
|
|
344
339
|
// Log detailed error
|
|
345
340
|
const errDetail = uploadErr.response?.data || uploadErr.message || uploadErr;
|
|
@@ -349,18 +344,15 @@ function createBot(config) {
|
|
|
349
344
|
if (isText) {
|
|
350
345
|
const content = fs.readFileSync(filePath, 'utf8');
|
|
351
346
|
const truncated = content.length > 3000 ? content.slice(0, 3000) + '\n...(truncated)' : content;
|
|
352
|
-
await this.sendMessage(chatId, `📄 ${fileName}:\n\`\`\`\n${truncated}\n\`\`\``);
|
|
347
|
+
const textMsg = await this.sendMessage(chatId, `📄 ${fileName}:\n\`\`\`\n${truncated}\n\`\`\``);
|
|
348
|
+
if (caption) await this.sendMessage(chatId, caption);
|
|
349
|
+
return textMsg || null;
|
|
353
350
|
} else {
|
|
354
351
|
// For binary files, give more helpful error
|
|
355
352
|
const errMsg = errDetail?.msg || errDetail?.message || '上传失败';
|
|
356
353
|
throw new Error(`${errMsg} (请检查飞书应用权限: im:resource)`);
|
|
357
354
|
}
|
|
358
355
|
}
|
|
359
|
-
|
|
360
|
-
// 3. Send caption as separate message if provided
|
|
361
|
-
if (caption) {
|
|
362
|
-
await this.sendMessage(chatId, caption);
|
|
363
|
-
}
|
|
364
356
|
},
|
|
365
357
|
|
|
366
358
|
/**
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Build a unified doc-routing hint for intent modules.
|
|
5
|
+
*
|
|
6
|
+
* @param {object} route
|
|
7
|
+
* @param {RegExp[]} route.patterns
|
|
8
|
+
* @param {string} route.title
|
|
9
|
+
* @param {string} route.docPath
|
|
10
|
+
* @param {string} route.summary
|
|
11
|
+
* @returns {(prompt: string) => string|null}
|
|
12
|
+
*/
|
|
13
|
+
function createDocRoute(route) {
|
|
14
|
+
const patterns = Array.isArray(route.patterns) ? route.patterns : [];
|
|
15
|
+
const title = String(route.title || '').trim();
|
|
16
|
+
const docPath = String(route.docPath || '').trim();
|
|
17
|
+
const summary = String(route.summary || '').trim();
|
|
18
|
+
|
|
19
|
+
if (!patterns.length || !title || !docPath || !summary) {
|
|
20
|
+
throw new Error('doc-router requires patterns, title, docPath, and summary');
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
return function detectDocRoute(prompt) {
|
|
24
|
+
if (!patterns.some((re) => re.test(prompt))) return null;
|
|
25
|
+
return `[${title}]\n- ${summary} → 先 \`cat ${docPath}\``;
|
|
26
|
+
};
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
module.exports = { createDocRoute };
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Doc Router Intent Module
|
|
5
|
+
*
|
|
6
|
+
* Detects documentation-oriented intents and routes the model
|
|
7
|
+
* to the right handbook/index with a unified hint format.
|
|
8
|
+
*
|
|
9
|
+
* @param {string} prompt
|
|
10
|
+
* @returns {string|null}
|
|
11
|
+
*/
|
|
12
|
+
|
|
13
|
+
const { createDocRoute } = require('./doc-router');
|
|
14
|
+
|
|
15
|
+
const routes = [
|
|
16
|
+
createDocRoute({
|
|
17
|
+
patterns: [
|
|
18
|
+
/(?:创建|新建|添加|注册|绑定|配置|管理).{0,8}(?:agent|机器人|bot|智能体)/i,
|
|
19
|
+
/(?:agent|bot|智能体).{0,8}(?:创建|新建|添加|注册|绑定|配置|管理)/i,
|
|
20
|
+
/\b(?:create|add|register|bind|manage|setup|configure)\s+(?:an?\s+)?agent\b/i,
|
|
21
|
+
],
|
|
22
|
+
title: 'Agent 管理提示',
|
|
23
|
+
docPath: '~/.metame/docs/agent-guide.md',
|
|
24
|
+
summary: '创建/管理/绑定 Agent',
|
|
25
|
+
}),
|
|
26
|
+
createDocRoute({
|
|
27
|
+
patterns: [
|
|
28
|
+
/(?:代码结构|脚本入口|升级进度|模块关系|文件结构)/,
|
|
29
|
+
/(?:pointer.?map|架构图|入口文件)/i,
|
|
30
|
+
],
|
|
31
|
+
title: '代码结构提示',
|
|
32
|
+
docPath: '~/.metame/docs/pointer-map.md',
|
|
33
|
+
summary: '代码结构/脚本入口/升级进度',
|
|
34
|
+
}),
|
|
35
|
+
createDocRoute({
|
|
36
|
+
patterns: [
|
|
37
|
+
/(?:hook|intent|意图).{0,10}(?:配置|设置|开关|新增|添加|修改|怎么配|怎么设置|怎么改)/i,
|
|
38
|
+
/(?:配置|设置|开关|新增|添加|修改).{0,10}(?:hook|intent|意图)/i,
|
|
39
|
+
/intent.?engine/i,
|
|
40
|
+
/意图引擎|意图模块/,
|
|
41
|
+
],
|
|
42
|
+
title: 'Intent Engine 配置提示',
|
|
43
|
+
docPath: '~/.metame/docs/hook-config.md',
|
|
44
|
+
summary: 'Hook/Intent 配置操作',
|
|
45
|
+
}),
|
|
46
|
+
];
|
|
47
|
+
|
|
48
|
+
module.exports = function detectDocRouter(prompt) {
|
|
49
|
+
const hints = routes
|
|
50
|
+
.map((detect) => detect(prompt))
|
|
51
|
+
.filter(Boolean);
|
|
52
|
+
|
|
53
|
+
return hints.length ? hints.join('\n') : null;
|
|
54
|
+
};
|
|
@@ -25,28 +25,7 @@ const os = require('os');
|
|
|
25
25
|
|
|
26
26
|
const METAME_DIR = path.join(os.homedir(), '.metame');
|
|
27
27
|
const { sanitizePrompt, isInternalPrompt } = require('./hook-utils');
|
|
28
|
-
|
|
29
|
-
// Default: all intents enabled unless explicitly set to false in daemon.yaml
|
|
30
|
-
const DEFAULTS = {
|
|
31
|
-
team_dispatch: true,
|
|
32
|
-
ops_assist: true,
|
|
33
|
-
task_create: true,
|
|
34
|
-
file_transfer: true,
|
|
35
|
-
memory_recall: true,
|
|
36
|
-
agent_manage: true,
|
|
37
|
-
hook_config: true,
|
|
38
|
-
};
|
|
39
|
-
|
|
40
|
-
// Intent registry — loaded lazily so startup is fast even if a module has issues
|
|
41
|
-
const INTENT_MODULES = {
|
|
42
|
-
team_dispatch: './intent-team-dispatch',
|
|
43
|
-
ops_assist: './intent-ops-assist',
|
|
44
|
-
task_create: './intent-task-create',
|
|
45
|
-
file_transfer: './intent-file-transfer',
|
|
46
|
-
memory_recall: './intent-memory-recall',
|
|
47
|
-
agent_manage: './intent-agent-manage',
|
|
48
|
-
hook_config: './intent-hook-config',
|
|
49
|
-
};
|
|
28
|
+
const { buildIntentHintBlock } = require('../intent-registry');
|
|
50
29
|
|
|
51
30
|
function exit() { process.exit(0); }
|
|
52
31
|
|
|
@@ -77,27 +56,17 @@ function run(data) {
|
|
|
77
56
|
config = yaml.load(fs.readFileSync(path.join(METAME_DIR, 'daemon.yaml'), 'utf8')) || {};
|
|
78
57
|
} catch { /* proceed with defaults */ }
|
|
79
58
|
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
for (const [key, modulePath] of Object.entries(INTENT_MODULES)) {
|
|
87
|
-
if (enabled[key] === false) continue;
|
|
88
|
-
try {
|
|
89
|
-
const detect = require(modulePath);
|
|
90
|
-
const result = detect(prompt, config, projectKey);
|
|
91
|
-
if (result) hints.push(result);
|
|
92
|
-
} catch (e) {
|
|
93
|
-
process.stderr.write(`[intent-engine] ${key}: ${e.message}\n`);
|
|
94
|
-
}
|
|
59
|
+
let intentBlock = '';
|
|
60
|
+
try {
|
|
61
|
+
intentBlock = buildIntentHintBlock(prompt, config, projectKey);
|
|
62
|
+
} catch (e) {
|
|
63
|
+
process.stderr.write(`[intent-engine] registry: ${e.message}\n`);
|
|
64
|
+
return exit();
|
|
95
65
|
}
|
|
96
|
-
|
|
97
|
-
if (hints.length === 0) return exit();
|
|
66
|
+
if (!intentBlock) return exit();
|
|
98
67
|
|
|
99
68
|
process.stdout.write(JSON.stringify({
|
|
100
|
-
hookSpecificOutput: { additionalSystemPrompt:
|
|
69
|
+
hookSpecificOutput: { additionalSystemPrompt: intentBlock },
|
|
101
70
|
}));
|
|
102
71
|
exit();
|
|
103
72
|
}
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Shared intent registry for all MetaMe runtime adapters.
|
|
5
|
+
*
|
|
6
|
+
* Detection logic lives here so Claude hooks and daemon-driven runtimes
|
|
7
|
+
* (for example Codex) stay behaviorally aligned.
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
const DEFAULTS = Object.freeze({
|
|
11
|
+
team_dispatch: true,
|
|
12
|
+
ops_assist: true,
|
|
13
|
+
task_create: true,
|
|
14
|
+
file_transfer: true,
|
|
15
|
+
memory_recall: true,
|
|
16
|
+
doc_router: true,
|
|
17
|
+
});
|
|
18
|
+
|
|
19
|
+
const INTENT_MODULES = Object.freeze({
|
|
20
|
+
team_dispatch: require('./hooks/intent-team-dispatch'),
|
|
21
|
+
ops_assist: require('./hooks/intent-ops-assist'),
|
|
22
|
+
task_create: require('./hooks/intent-task-create'),
|
|
23
|
+
file_transfer: require('./hooks/intent-file-transfer'),
|
|
24
|
+
memory_recall: require('./hooks/intent-memory-recall'),
|
|
25
|
+
doc_router: require('./hooks/intent-doc-router'),
|
|
26
|
+
});
|
|
27
|
+
|
|
28
|
+
function resolveEnabledIntents(config = {}) {
|
|
29
|
+
const hooksCfg = (config.hooks && typeof config.hooks === 'object') ? config.hooks : {};
|
|
30
|
+
return { ...DEFAULTS, ...hooksCfg };
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
function collectIntentHints(prompt, config = {}, projectKey = '') {
|
|
34
|
+
const text = String(prompt || '').trim();
|
|
35
|
+
if (!text) return [];
|
|
36
|
+
|
|
37
|
+
const enabled = resolveEnabledIntents(config);
|
|
38
|
+
const hints = [];
|
|
39
|
+
for (const [key, detect] of Object.entries(INTENT_MODULES)) {
|
|
40
|
+
if (enabled[key] === false) continue;
|
|
41
|
+
const hint = detect(text, config, projectKey);
|
|
42
|
+
if (hint) hints.push({ key, hint });
|
|
43
|
+
}
|
|
44
|
+
return hints;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
function buildIntentHintBlock(prompt, config = {}, projectKey = '') {
|
|
48
|
+
return collectIntentHints(prompt, config, projectKey)
|
|
49
|
+
.map(item => item.hint)
|
|
50
|
+
.join('\n\n');
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
module.exports = {
|
|
54
|
+
DEFAULTS,
|
|
55
|
+
INTENT_MODULES,
|
|
56
|
+
resolveEnabledIntents,
|
|
57
|
+
collectIntentHints,
|
|
58
|
+
buildIntentHintBlock,
|
|
59
|
+
};
|
|
@@ -346,6 +346,65 @@ async function run() {
|
|
|
346
346
|
}
|
|
347
347
|
}
|
|
348
348
|
|
|
349
|
+
// ── Codex sessions ──────────────────────────────────────────────────────
|
|
350
|
+
// Same pipeline, different source: reads ~/.codex/sessions rollout files
|
|
351
|
+
// (first 2KB only) + history.jsonl for user messages.
|
|
352
|
+
const codexSessions = sessionAnalytics.findAllUnextractedCodexSessions(3);
|
|
353
|
+
if (codexSessions.length > 0) {
|
|
354
|
+
// Pass session IDs so loadCodexHistory only parses relevant entries
|
|
355
|
+
// (history.jsonl grows unbounded; no need to load the full file)
|
|
356
|
+
const historyMap = sessionAnalytics.loadCodexHistory(codexSessions.map(cs => cs.session_id));
|
|
357
|
+
for (const cs of codexSessions) {
|
|
358
|
+
try {
|
|
359
|
+
const { skeleton, evidence } = sessionAnalytics.buildCodexInput(cs.path, historyMap);
|
|
360
|
+
|
|
361
|
+
// Skip trivial sessions with no user messages
|
|
362
|
+
if (skeleton.message_count < 1) {
|
|
363
|
+
sessionAnalytics.markCodexFactsExtracted(cs.session_id);
|
|
364
|
+
continue;
|
|
365
|
+
}
|
|
366
|
+
|
|
367
|
+
const { ok, facts, session_name } = await extractFacts(skeleton, evidence, distillEnv);
|
|
368
|
+
if (!ok) {
|
|
369
|
+
console.log(`[memory-extract] Codex ${cs.session_id.slice(0, 8)}: extraction failed, will retry later`);
|
|
370
|
+
continue;
|
|
371
|
+
}
|
|
372
|
+
|
|
373
|
+
if (facts.length > 0) {
|
|
374
|
+
const fallbackScope = `codex_${String(cs.session_id).replace(/[^a-zA-Z0-9_-]/g, '').slice(0, 24)}`;
|
|
375
|
+
const { saved, skipped, superseded, savedFacts } = memory.saveFacts(
|
|
376
|
+
cs.session_id,
|
|
377
|
+
skeleton.project || 'unknown',
|
|
378
|
+
facts,
|
|
379
|
+
{ scope: skeleton.project_id || fallbackScope, source_type: 'codex' }
|
|
380
|
+
);
|
|
381
|
+
let labelsSaved = 0;
|
|
382
|
+
if (typeof memory.saveFactLabels === 'function' && Array.isArray(savedFacts) && savedFacts.length > 0) {
|
|
383
|
+
const labelRows = buildFactLabelRows(facts, savedFacts);
|
|
384
|
+
if (labelRows.length > 0) {
|
|
385
|
+
const lr = memory.saveFactLabels(labelRows);
|
|
386
|
+
labelsSaved = Number(lr && lr.saved) || 0;
|
|
387
|
+
}
|
|
388
|
+
}
|
|
389
|
+
totalSaved += saved;
|
|
390
|
+
totalSkipped += skipped;
|
|
391
|
+
const superMsg = superseded > 0 ? `, ${superseded} superseded` : '';
|
|
392
|
+
const labelMsg = labelsSaved > 0 ? `, ${labelsSaved} labels` : '';
|
|
393
|
+
console.log(`[memory-extract] Codex ${cs.session_id.slice(0, 8)} (${session_name}): ${saved} facts saved${superMsg}${labelMsg}`);
|
|
394
|
+
} else {
|
|
395
|
+
console.log(`[memory-extract] Codex ${cs.session_id.slice(0, 8)} (${session_name}): no facts extracted`);
|
|
396
|
+
}
|
|
397
|
+
|
|
398
|
+
sessionAnalytics.markCodexFactsExtracted(cs.session_id);
|
|
399
|
+
saveSessionTag(cs.session_id, session_name, facts);
|
|
400
|
+
processed++;
|
|
401
|
+
} catch (e) {
|
|
402
|
+
console.log(`[memory-extract] Codex session error: ${e.message}`);
|
|
403
|
+
}
|
|
404
|
+
}
|
|
405
|
+
}
|
|
406
|
+
// ── end Codex ────────────────────────────────────────────────────────────
|
|
407
|
+
|
|
349
408
|
memory.close();
|
|
350
409
|
return { sessionsProcessed: processed, factsSaved: totalSaved, factsSkipped: totalSkipped };
|
|
351
410
|
} finally {
|
package/scripts/mentor-engine.js
CHANGED
|
@@ -54,6 +54,11 @@ function saveRuntime(runtime) {
|
|
|
54
54
|
fs.renameSync(tmp, file);
|
|
55
55
|
}
|
|
56
56
|
|
|
57
|
+
function clearRuntime() {
|
|
58
|
+
saveRuntime(defaultRuntime());
|
|
59
|
+
return defaultRuntime();
|
|
60
|
+
}
|
|
61
|
+
|
|
57
62
|
function normalizeText(input) {
|
|
58
63
|
return String(input || '').trim();
|
|
59
64
|
}
|
|
@@ -394,6 +399,7 @@ module.exports = {
|
|
|
394
399
|
gcExpiredDebts,
|
|
395
400
|
detectPatterns,
|
|
396
401
|
getRuntimeStatus,
|
|
402
|
+
clearRuntime,
|
|
397
403
|
_private: {
|
|
398
404
|
runtimeFilePath,
|
|
399
405
|
loadRuntime,
|
package/scripts/schema.js
CHANGED
|
@@ -103,6 +103,7 @@ const SCHEMA = {
|
|
|
103
103
|
|
|
104
104
|
// === T5: Growth (metacognition, system-managed) ===
|
|
105
105
|
'growth.patterns': { tier: 'T5', type: 'array', maxItems: 3 },
|
|
106
|
+
'growth.self_reflection_patterns': { tier: 'T5', type: 'array', maxItems: 3 },
|
|
106
107
|
'growth.zone_history': { tier: 'T5', type: 'array', maxItems: 10 },
|
|
107
108
|
'growth.reflections_answered': { tier: 'T5', type: 'number' },
|
|
108
109
|
'growth.reflections_skipped': { tier: 'T5', type: 'number' },
|
package/scripts/self-reflect.js
CHANGED
|
@@ -5,7 +5,8 @@
|
|
|
5
5
|
*
|
|
6
6
|
* Scans correction/metacognitive signals from the past 7 days,
|
|
7
7
|
* aggregates "where did the AI get it wrong", and writes a brief
|
|
8
|
-
* self-critique pattern into growth.
|
|
8
|
+
* self-critique pattern into growth.self_reflection_patterns
|
|
9
|
+
* in ~/.claude_profile.yaml.
|
|
9
10
|
*
|
|
10
11
|
* Also distills correction signals into lessons/ SOP markdown files.
|
|
11
12
|
*
|
|
@@ -27,6 +28,81 @@ const LOCK_FILE = path.join(HOME, '.metame', 'self-reflect.lock');
|
|
|
27
28
|
const LESSONS_DIR = path.join(HOME, '.metame', 'memory', 'lessons');
|
|
28
29
|
const WINDOW_DAYS = 7;
|
|
29
30
|
|
|
31
|
+
function normalizeReflectionEntry(entry) {
|
|
32
|
+
if (!entry) return null;
|
|
33
|
+
if (typeof entry === 'string') {
|
|
34
|
+
const summary = entry.trim();
|
|
35
|
+
if (!summary) return null;
|
|
36
|
+
return {
|
|
37
|
+
summary,
|
|
38
|
+
detected: new Date().toISOString().slice(0, 10),
|
|
39
|
+
};
|
|
40
|
+
}
|
|
41
|
+
if (typeof entry === 'object' && typeof entry.summary === 'string' && entry.summary.trim()) {
|
|
42
|
+
return {
|
|
43
|
+
summary: entry.summary.trim(),
|
|
44
|
+
detected: entry.detected || new Date().toISOString().slice(0, 10),
|
|
45
|
+
};
|
|
46
|
+
}
|
|
47
|
+
return null;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
function mergeReflectionEntries(entries) {
|
|
51
|
+
const merged = new Map();
|
|
52
|
+
for (const entry of entries) {
|
|
53
|
+
const normalized = normalizeReflectionEntry(entry);
|
|
54
|
+
if (!normalized) continue;
|
|
55
|
+
const existing = merged.get(normalized.summary);
|
|
56
|
+
if (!existing) {
|
|
57
|
+
merged.set(normalized.summary, normalized);
|
|
58
|
+
continue;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
const existingDetected = existing.detected ? new Date(existing.detected).getTime() : 0;
|
|
62
|
+
const normalizedDetected = normalized.detected ? new Date(normalized.detected).getTime() : 0;
|
|
63
|
+
const shouldReplace =
|
|
64
|
+
normalizedDetected > 0 && (
|
|
65
|
+
existingDetected === 0
|
|
66
|
+
|| normalizedDetected < existingDetected
|
|
67
|
+
);
|
|
68
|
+
if (shouldReplace) merged.set(normalized.summary, { ...existing, ...normalized });
|
|
69
|
+
}
|
|
70
|
+
return [...merged.values()];
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
function normalizeSelfReflectionPatterns(profile) {
|
|
74
|
+
if (!profile.growth) profile.growth = {};
|
|
75
|
+
|
|
76
|
+
const current = Array.isArray(profile.growth.self_reflection_patterns)
|
|
77
|
+
? profile.growth.self_reflection_patterns
|
|
78
|
+
: [];
|
|
79
|
+
const legacy = Array.isArray(profile.growth.patterns)
|
|
80
|
+
? profile.growth.patterns.filter(p => typeof p === 'string')
|
|
81
|
+
: [];
|
|
82
|
+
|
|
83
|
+
const normalized = mergeReflectionEntries([...current, ...legacy])
|
|
84
|
+
.slice(-3);
|
|
85
|
+
|
|
86
|
+
profile.growth.self_reflection_patterns = normalized;
|
|
87
|
+
if (Array.isArray(profile.growth.patterns)) {
|
|
88
|
+
profile.growth.patterns = profile.growth.patterns.filter(p => typeof p === 'object' && p && typeof p.summary === 'string');
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
return normalized;
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
function migrateLegacySelfReflectionPatterns(profile) {
|
|
95
|
+
const beforePatterns = JSON.stringify((profile.growth && profile.growth.patterns) || null);
|
|
96
|
+
const beforeReflections = JSON.stringify((profile.growth && profile.growth.self_reflection_patterns) || null);
|
|
97
|
+
const normalized = normalizeSelfReflectionPatterns(profile);
|
|
98
|
+
const afterPatterns = JSON.stringify((profile.growth && profile.growth.patterns) || null);
|
|
99
|
+
const afterReflections = JSON.stringify((profile.growth && profile.growth.self_reflection_patterns) || null);
|
|
100
|
+
return {
|
|
101
|
+
changed: beforePatterns !== afterPatterns || beforeReflections !== afterReflections,
|
|
102
|
+
normalized,
|
|
103
|
+
};
|
|
104
|
+
}
|
|
105
|
+
|
|
30
106
|
/**
|
|
31
107
|
* Distill correction signals into reusable SOP markdown files.
|
|
32
108
|
* Each run produces at most one lesson file per unique slug.
|
|
@@ -148,6 +224,22 @@ async function run() {
|
|
|
148
224
|
}
|
|
149
225
|
|
|
150
226
|
try {
|
|
227
|
+
// Persist legacy string-pattern migration even when this run produces no new reflections.
|
|
228
|
+
if (fs.existsSync(BRAIN_FILE)) {
|
|
229
|
+
try {
|
|
230
|
+
const yaml = require('js-yaml');
|
|
231
|
+
const profile = yaml.load(fs.readFileSync(BRAIN_FILE, 'utf8')) || {};
|
|
232
|
+
const migrated = migrateLegacySelfReflectionPatterns(profile);
|
|
233
|
+
if (migrated.changed) {
|
|
234
|
+
const dumped = yaml.dump(profile, { lineWidth: -1 });
|
|
235
|
+
await writeBrainFileSafe(dumped);
|
|
236
|
+
console.log('[self-reflect] Migrated legacy growth.patterns strings into growth.self_reflection_patterns.');
|
|
237
|
+
}
|
|
238
|
+
} catch (e) {
|
|
239
|
+
console.log(`[self-reflect] Legacy pattern migration failed (non-fatal): ${e.message}`);
|
|
240
|
+
}
|
|
241
|
+
}
|
|
242
|
+
|
|
151
243
|
// Read signals from last WINDOW_DAYS days
|
|
152
244
|
if (!fs.existsSync(SIGNAL_FILE)) {
|
|
153
245
|
console.log('[self-reflect] No signal file, skipping.');
|
|
@@ -175,9 +267,9 @@ async function run() {
|
|
|
175
267
|
try {
|
|
176
268
|
const yaml = require('js-yaml');
|
|
177
269
|
const profile = yaml.load(fs.readFileSync(BRAIN_FILE, 'utf8')) || {};
|
|
178
|
-
const existing = (profile.
|
|
270
|
+
const existing = migrateLegacySelfReflectionPatterns(profile).normalized;
|
|
179
271
|
if (existing.length > 0) {
|
|
180
|
-
currentPatterns = `Current growth.
|
|
272
|
+
currentPatterns = `Current growth.self_reflection_patterns (avoid repeating):\n${existing.map(p => `- ${p.summary}`).join('\n')}\n\n`;
|
|
181
273
|
}
|
|
182
274
|
} catch { /* non-fatal */ }
|
|
183
275
|
|
|
@@ -247,24 +339,24 @@ ${signalText}
|
|
|
247
339
|
return;
|
|
248
340
|
}
|
|
249
341
|
|
|
250
|
-
// Merge into growth.
|
|
342
|
+
// Merge into growth.self_reflection_patterns (cap at 3, keep newest)
|
|
251
343
|
try {
|
|
252
344
|
const yaml = require('js-yaml');
|
|
253
345
|
const raw = fs.readFileSync(BRAIN_FILE, 'utf8');
|
|
254
346
|
const profile = yaml.load(raw) || {};
|
|
255
347
|
if (!profile.growth) profile.growth = {};
|
|
256
|
-
const existing =
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
.
|
|
348
|
+
const existing = migrateLegacySelfReflectionPatterns(profile).normalized;
|
|
349
|
+
const merged = mergeReflectionEntries([
|
|
350
|
+
...existing,
|
|
351
|
+
...patterns.map(summary => ({ summary, detected: new Date().toISOString().slice(0, 10) })),
|
|
352
|
+
])
|
|
260
353
|
.slice(-3);
|
|
261
|
-
profile.growth.
|
|
354
|
+
profile.growth.self_reflection_patterns = merged;
|
|
262
355
|
profile.growth.last_reflection = new Date().toISOString().slice(0, 10);
|
|
263
356
|
|
|
264
|
-
// Preserve locked lines (simple approach: only update growth section)
|
|
265
357
|
const dumped = yaml.dump(profile, { lineWidth: -1 });
|
|
266
358
|
await writeBrainFileSafe(dumped);
|
|
267
|
-
console.log(`[self-reflect] ${patterns.length} pattern(s) written to growth.
|
|
359
|
+
console.log(`[self-reflect] ${patterns.length} pattern(s) written to growth.self_reflection_patterns: ${patterns.join(' | ')}`);
|
|
268
360
|
} catch (e) {
|
|
269
361
|
console.log(`[self-reflect] Failed to write profile: ${e.message}`);
|
|
270
362
|
}
|
|
@@ -283,4 +375,10 @@ if (require.main === module) {
|
|
|
283
375
|
});
|
|
284
376
|
}
|
|
285
377
|
|
|
286
|
-
module.exports = {
|
|
378
|
+
module.exports = {
|
|
379
|
+
run,
|
|
380
|
+
mergeReflectionEntries,
|
|
381
|
+
normalizeReflectionEntry,
|
|
382
|
+
normalizeSelfReflectionPatterns,
|
|
383
|
+
migrateLegacySelfReflectionPatterns,
|
|
384
|
+
};
|