team-anya-cli 0.1.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/README.md +38 -0
- package/anya/prompts/execution-guides/git-delivery.md +38 -0
- package/anya/prompts/execution-guides/testing-and-self-heal.md +28 -0
- package/anya/prompts/protocols/brief-assembly.md +55 -0
- package/anya/prompts/protocols/report.md +175 -0
- package/anya/prompts/protocols/review.md +90 -0
- package/anya/prompts/task-claude-md.template.md +32 -0
- package/apps/server/dist/broker/cc-broker.js +257 -0
- package/apps/server/dist/cli.js +296 -0
- package/apps/server/dist/config.js +76 -0
- package/apps/server/dist/daemon.js +51 -0
- package/apps/server/dist/gateway/chat-sync.js +135 -0
- package/apps/server/dist/gateway/command-router.js +114 -0
- package/apps/server/dist/gateway/commands/cancel.js +32 -0
- package/apps/server/dist/gateway/commands/help.js +16 -0
- package/apps/server/dist/gateway/commands/index.js +26 -0
- package/apps/server/dist/gateway/commands/restart.js +34 -0
- package/apps/server/dist/gateway/commands/status.js +34 -0
- package/apps/server/dist/gateway/commands/tasks.js +33 -0
- package/apps/server/dist/gateway/feishu-sender.js +346 -0
- package/apps/server/dist/gateway/feishu-ws.js +254 -0
- package/apps/server/dist/gateway/http.js +994 -0
- package/apps/server/dist/gateway/media-downloader.js +149 -0
- package/apps/server/dist/gateway/message-events.js +10 -0
- package/apps/server/dist/gateway/message-intake.js +50 -0
- package/apps/server/dist/gateway/message-queue.js +104 -0
- package/apps/server/dist/gateway/session-reader.js +142 -0
- package/apps/server/dist/gateway/ws-push.js +115 -0
- package/apps/server/dist/loid/brain.js +104 -0
- package/apps/server/dist/loid/clarifier.js +162 -0
- package/apps/server/dist/loid/context-builder.js +413 -0
- package/apps/server/dist/loid/mcp-server.js +104 -0
- package/apps/server/dist/loid/memory-settler.js +189 -0
- package/apps/server/dist/loid/opportunity-manager.js +148 -0
- package/apps/server/dist/loid/profile-updater.js +179 -0
- package/apps/server/dist/loid/reporter.js +148 -0
- package/apps/server/dist/loid/schemas.js +117 -0
- package/apps/server/dist/loid/self-calibrator.js +314 -0
- package/apps/server/dist/loid/session-manager.js +217 -0
- package/apps/server/dist/loid/session.js +271 -0
- package/apps/server/dist/loid/worktree-manager.js +191 -0
- package/apps/server/dist/main.js +337 -0
- package/apps/server/dist/tracing/index.js +2 -0
- package/apps/server/dist/tracing/trace-context.js +92 -0
- package/apps/server/dist/types/message.js +2 -0
- package/apps/server/dist/yor/yor-mcp-server.js +104 -0
- package/apps/server/dist/yor/yor-orchestrator.js +233 -0
- package/apps/web/dist/assets/index-CHIT0Dya.css +1 -0
- package/apps/web/dist/assets/index-CJzAjoVH.js +798 -0
- package/apps/web/dist/index.html +13 -0
- package/package.json +42 -0
- package/packages/cc-client/dist/claude-code-backend.js +664 -0
- package/packages/cc-client/dist/index.js +2 -0
- package/packages/cc-client/package.json +11 -0
- package/packages/core/dist/constants.js +59 -0
- package/packages/core/dist/errors.js +35 -0
- package/packages/core/dist/index.js +7 -0
- package/packages/core/dist/office-init.js +97 -0
- package/packages/core/dist/scope/checker.js +114 -0
- package/packages/core/dist/scope/defaults.js +40 -0
- package/packages/core/dist/scope/index.js +3 -0
- package/packages/core/dist/state-machine.js +85 -0
- package/packages/core/dist/types/audit.js +12 -0
- package/packages/core/dist/types/backend.js +2 -0
- package/packages/core/dist/types/commitment.js +17 -0
- package/packages/core/dist/types/communication.js +18 -0
- package/packages/core/dist/types/index.js +8 -0
- package/packages/core/dist/types/opportunity.js +27 -0
- package/packages/core/dist/types/org.js +26 -0
- package/packages/core/dist/types/task.js +46 -0
- package/packages/core/package.json +10 -0
- package/packages/db/dist/client.js +69 -0
- package/packages/db/dist/index.js +603 -0
- package/packages/db/dist/schema/audit-events.js +13 -0
- package/packages/db/dist/schema/cc-sessions.js +14 -0
- package/packages/db/dist/schema/chats.js +33 -0
- package/packages/db/dist/schema/commitments.js +18 -0
- package/packages/db/dist/schema/communication-events.js +14 -0
- package/packages/db/dist/schema/index.js +12 -0
- package/packages/db/dist/schema/message-log.js +20 -0
- package/packages/db/dist/schema/opportunities.js +23 -0
- package/packages/db/dist/schema/org.js +36 -0
- package/packages/db/dist/schema/projects.js +23 -0
- package/packages/db/dist/schema/tasks.js +46 -0
- package/packages/db/dist/schema/trace-spans.js +19 -0
- package/packages/db/package.json +12 -0
- package/packages/db/src/migrations/0000_simple_magneto.sql +148 -0
- package/packages/db/src/migrations/0001_nifty_morph.sql +42 -0
- package/packages/db/src/migrations/0002_common_joshua_kane.sql +20 -0
- package/packages/db/src/migrations/0003_add_cc_sessions.sql +13 -0
- package/packages/db/src/migrations/0004_jittery_triathlon.sql +1 -0
- package/packages/db/src/migrations/meta/0000_snapshot.json +987 -0
- package/packages/db/src/migrations/meta/0001_snapshot.json +1280 -0
- package/packages/db/src/migrations/meta/0002_snapshot.json +1417 -0
- package/packages/db/src/migrations/meta/0004_snapshot.json +1505 -0
- package/packages/db/src/migrations/meta/_journal.json +41 -0
- package/packages/mcp-tools/dist/index.js +41 -0
- package/packages/mcp-tools/dist/layer1/audit-append.js +38 -0
- package/packages/mcp-tools/dist/layer1/audit-query.js +51 -0
- package/packages/mcp-tools/dist/layer1/memory-brief.js +168 -0
- package/packages/mcp-tools/dist/layer1/memory-context.js +124 -0
- package/packages/mcp-tools/dist/layer1/memory-digest.js +126 -0
- package/packages/mcp-tools/dist/layer1/memory-forget.js +108 -0
- package/packages/mcp-tools/dist/layer1/memory-learn.js +63 -0
- package/packages/mcp-tools/dist/layer1/memory-recall.js +287 -0
- package/packages/mcp-tools/dist/layer1/memory-reflect.js +80 -0
- package/packages/mcp-tools/dist/layer1/memory-remember.js +119 -0
- package/packages/mcp-tools/dist/layer1/memory-search.js +263 -0
- package/packages/mcp-tools/dist/layer1/memory-write.js +21 -0
- package/packages/mcp-tools/dist/layer1/org-lookup.js +47 -0
- package/packages/mcp-tools/dist/layer1/project-get.js +28 -0
- package/packages/mcp-tools/dist/layer1/project-list.js +20 -0
- package/packages/mcp-tools/dist/layer1/report-daily.js +68 -0
- package/packages/mcp-tools/dist/layer1/task-get.js +29 -0
- package/packages/mcp-tools/dist/layer1/task-update.js +34 -0
- package/packages/mcp-tools/dist/layer2/loid/decision-log.js +15 -0
- package/packages/mcp-tools/dist/layer2/loid/decision-no-action.js +15 -0
- package/packages/mcp-tools/dist/layer2/loid/delivery-create-pr.js +30 -0
- package/packages/mcp-tools/dist/layer2/loid/delivery-share.js +12 -0
- package/packages/mcp-tools/dist/layer2/loid/delivery-submit.js +77 -0
- package/packages/mcp-tools/dist/layer2/loid/delivery-upload.js +18 -0
- package/packages/mcp-tools/dist/layer2/loid/project-remove.js +16 -0
- package/packages/mcp-tools/dist/layer2/loid/project-upsert.js +33 -0
- package/packages/mcp-tools/dist/layer2/loid/task-dispatch.js +177 -0
- package/packages/mcp-tools/dist/layer2/loid/task-lookup.js +38 -0
- package/packages/mcp-tools/dist/layer2/loid/yor-approve.js +8 -0
- package/packages/mcp-tools/dist/layer2/loid/yor-kill.js +7 -0
- package/packages/mcp-tools/dist/layer2/loid/yor-rework.js +7 -0
- package/packages/mcp-tools/dist/layer2/loid/yor-spawn.js +15 -0
- package/packages/mcp-tools/dist/layer2/loid/yor-status.js +8 -0
- package/packages/mcp-tools/dist/layer2/yor/task-block.js +11 -0
- package/packages/mcp-tools/dist/layer2/yor/task-deliver.js +35 -0
- package/packages/mcp-tools/dist/layer2/yor/task-progress.js +21 -0
- package/packages/mcp-tools/dist/layer3/adapters/feishu-adapter.js +191 -0
- package/packages/mcp-tools/dist/layer3/adapters/types.js +28 -0
- package/packages/mcp-tools/dist/layer3/channel-receive.js +11 -0
- package/packages/mcp-tools/dist/layer3/channel-send.js +90 -0
- package/packages/mcp-tools/dist/layer3/file-upload.js +44 -0
- package/packages/mcp-tools/dist/registry.js +779 -0
- package/packages/mcp-tools/package.json +13 -0
- package/workspace/.claude/settings.local.json +9 -0
- package/workspace/.mcp.json +12 -0
- package/workspace/CHARTER.md +73 -0
- package/workspace/CLAUDE.md +49 -0
- package/workspace/PROTOCOL.md +126 -0
- package/workspace/TOOLS.md +464 -0
- package/workspace/audit/.gitkeep +0 -0
- package/workspace/loid/CLAUDE.md +12 -0
- package/workspace/loid/PLAYBOOK.md +198 -0
- package/workspace/loid/PROFILE.md +78 -0
- package/workspace/memory/commitments/.gitkeep +0 -0
- package/workspace/memory/execution/.gitkeep +0 -0
- package/workspace/memory/people/.gitkeep +0 -0
- package/workspace/memory/projects/.gitkeep +0 -0
- package/workspace/memory/self/.gitkeep +0 -0
- package/workspace/reference/identity/.gitkeep +0 -0
- package/workspace/reference/org/escalation.yaml +24 -0
- package/workspace/reference/org/ownership.yaml +28 -0
- package/workspace/reports/.gitkeep +0 -0
- package/workspace/yor/CLAUDE.md +22 -0
- package/workspace/yor/PLAYBOOK.md +73 -0
- package/workspace/yor/PROFILE.md +52 -0
- package/workspace/yor/SELF-HEAL.md +39 -0
|
@@ -0,0 +1,108 @@
|
|
|
1
|
+
import { insertAuditEvent } from '@team-anya/db';
|
|
2
|
+
import { readFile, writeFile, readdir } from 'node:fs/promises';
|
|
3
|
+
import { join } from 'node:path';
|
|
4
|
+
import * as yaml from 'yaml';
|
|
5
|
+
/**
|
|
6
|
+
* 遗忘/失效记忆(新增工具)
|
|
7
|
+
*
|
|
8
|
+
* 当前只实现 invalidate 模式:将匹配条目的 heat 设为 0。
|
|
9
|
+
* // TODO: 后续实现 decay(定时衰减)和 merge(合并相似记忆)模式
|
|
10
|
+
*/
|
|
11
|
+
export async function memoryForget(db, workspacePath, input) {
|
|
12
|
+
if (input.mode !== 'invalidate') {
|
|
13
|
+
throw new Error(`不支持的遗忘模式: ${input.mode},当前仅支持 invalidate`);
|
|
14
|
+
}
|
|
15
|
+
const details = [];
|
|
16
|
+
let affectedCount = 0;
|
|
17
|
+
// 确定搜索范围
|
|
18
|
+
const memoryBase = join(workspacePath, 'memory');
|
|
19
|
+
const searchDir = input.scope
|
|
20
|
+
? join(memoryBase, input.scope)
|
|
21
|
+
: memoryBase;
|
|
22
|
+
// 查找所有 yaml 文件
|
|
23
|
+
const yamlFiles = await findYamlFiles(searchDir);
|
|
24
|
+
for (const filePath of yamlFiles) {
|
|
25
|
+
try {
|
|
26
|
+
const content = await readFile(filePath, 'utf-8');
|
|
27
|
+
const doc = yaml.parse(content);
|
|
28
|
+
if (!doc)
|
|
29
|
+
continue;
|
|
30
|
+
// 获取条目列表
|
|
31
|
+
const listKey = doc.entries ? 'entries' : doc.observations ? 'observations' : doc.active ? 'active' : null;
|
|
32
|
+
if (!listKey || !Array.isArray(doc[listKey]))
|
|
33
|
+
continue;
|
|
34
|
+
let changed = false;
|
|
35
|
+
for (const entry of doc[listKey]) {
|
|
36
|
+
if (!input.target) {
|
|
37
|
+
// 无 target:失效所有条目
|
|
38
|
+
if (entry.meta) {
|
|
39
|
+
entry.meta.heat = 0;
|
|
40
|
+
changed = true;
|
|
41
|
+
affectedCount++;
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
else {
|
|
45
|
+
// 有 target:匹配内容关键词
|
|
46
|
+
const entryContent = typeof entry === 'string' ? entry : (entry.content ?? '');
|
|
47
|
+
if (entryContent.includes(input.target)) {
|
|
48
|
+
if (entry.meta) {
|
|
49
|
+
entry.meta.heat = 0;
|
|
50
|
+
}
|
|
51
|
+
else {
|
|
52
|
+
entry.meta = { heat: 0 };
|
|
53
|
+
}
|
|
54
|
+
changed = true;
|
|
55
|
+
affectedCount++;
|
|
56
|
+
details.push(`已失效: ${entryContent.slice(0, 80)}`);
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
if (changed) {
|
|
61
|
+
await writeFile(filePath, yaml.stringify(doc), 'utf-8');
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
catch {
|
|
65
|
+
// 文件处理失败,继续下一个
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
// 写审计事件
|
|
69
|
+
try {
|
|
70
|
+
insertAuditEvent(db, {
|
|
71
|
+
event_type: 'memory_invalidated',
|
|
72
|
+
actor: 'system',
|
|
73
|
+
task_id: null,
|
|
74
|
+
summary: `记忆失效: scope=${input.scope ?? 'all'}, target=${input.target ?? 'all'}, affected=${affectedCount}`,
|
|
75
|
+
detail: JSON.stringify({
|
|
76
|
+
mode: input.mode,
|
|
77
|
+
scope: input.scope,
|
|
78
|
+
target: input.target,
|
|
79
|
+
reason: input.reason,
|
|
80
|
+
affected_count: affectedCount,
|
|
81
|
+
}),
|
|
82
|
+
});
|
|
83
|
+
}
|
|
84
|
+
catch {
|
|
85
|
+
// 审计失败不阻塞
|
|
86
|
+
}
|
|
87
|
+
return {
|
|
88
|
+
success: true,
|
|
89
|
+
affected_count: affectedCount,
|
|
90
|
+
details,
|
|
91
|
+
};
|
|
92
|
+
}
|
|
93
|
+
async function findYamlFiles(dir) {
|
|
94
|
+
const files = [];
|
|
95
|
+
try {
|
|
96
|
+
const entries = await readdir(dir, { withFileTypes: true, recursive: true });
|
|
97
|
+
for (const entry of entries) {
|
|
98
|
+
if (entry.isFile() && (entry.name.endsWith('.yaml') || entry.name.endsWith('.yml'))) {
|
|
99
|
+
files.push(join(entry.parentPath, entry.name));
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
catch {
|
|
104
|
+
// 目录不存在
|
|
105
|
+
}
|
|
106
|
+
return files;
|
|
107
|
+
}
|
|
108
|
+
//# sourceMappingURL=memory-forget.js.map
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
import { insertAuditEvent } from '@team-anya/db';
|
|
2
|
+
import { appendFile, mkdir } from 'node:fs/promises';
|
|
3
|
+
import { join } from 'node:path';
|
|
4
|
+
const VALID_TYPES = ['task', 'decision', 'failure', 'project', 'org', 'insight', 'commitment', 'opportunity'];
|
|
5
|
+
/**
|
|
6
|
+
* 主动写入记忆
|
|
7
|
+
*
|
|
8
|
+
* 写入 DB memories 表 + 审计事件。
|
|
9
|
+
* 替代原有的 memory-write(纯文件模式),改为结构化存储。
|
|
10
|
+
*
|
|
11
|
+
* 降级策略:DB 尚无 memories 表时,降级写入 memory/ 目录的文件 + 审计事件。
|
|
12
|
+
*/
|
|
13
|
+
export async function memoryLearn(db, workspacePath, input) {
|
|
14
|
+
// 校验 type
|
|
15
|
+
if (!VALID_TYPES.includes(input.type)) {
|
|
16
|
+
throw new Error(`无效的记忆类型: ${input.type},有效值: ${VALID_TYPES.join(', ')}`);
|
|
17
|
+
}
|
|
18
|
+
// 校验 tags
|
|
19
|
+
if (!input.tags || input.tags.length < 1) {
|
|
20
|
+
throw new Error('至少需要 1 个 tag');
|
|
21
|
+
}
|
|
22
|
+
const importance = input.importance ?? 0.5;
|
|
23
|
+
if (importance < 0 || importance > 1) {
|
|
24
|
+
throw new Error('importance 必须在 0-1 之间');
|
|
25
|
+
}
|
|
26
|
+
// 降级策略:写文件 + 审计事件
|
|
27
|
+
// 当 memories 表就绪后,这里改为 DB 写入
|
|
28
|
+
const memoryDir = join(workspacePath, 'memory', input.type === 'project' && input.project_id
|
|
29
|
+
? `projects/${input.project_id}`
|
|
30
|
+
: input.type === 'decision' ? 'decisions'
|
|
31
|
+
: input.type === 'org' ? 'org'
|
|
32
|
+
: 'self');
|
|
33
|
+
await mkdir(memoryDir, { recursive: true });
|
|
34
|
+
const timestamp = new Date().toISOString();
|
|
35
|
+
const fileName = `${input.type}-${timestamp.slice(0, 10)}.md`;
|
|
36
|
+
const filePath = join(memoryDir, fileName);
|
|
37
|
+
const header = [
|
|
38
|
+
`<!-- type: ${input.type} -->`,
|
|
39
|
+
`<!-- tags: ${input.tags.join(', ')} -->`,
|
|
40
|
+
`<!-- importance: ${importance} -->`,
|
|
41
|
+
input.task_id ? `<!-- task: ${input.task_id} -->` : '',
|
|
42
|
+
input.project_id ? `<!-- project: ${input.project_id} -->` : '',
|
|
43
|
+
input.expires_at ? `<!-- expires: ${input.expires_at} -->` : '',
|
|
44
|
+
`<!-- created: ${timestamp} -->`,
|
|
45
|
+
'',
|
|
46
|
+
].filter(Boolean).join('\n');
|
|
47
|
+
await appendFile(filePath, `\n${header}\n${input.content}\n\n---\n`);
|
|
48
|
+
// 写审计事件
|
|
49
|
+
const result = insertAuditEvent(db, {
|
|
50
|
+
event_type: 'memory_learned',
|
|
51
|
+
actor: 'system',
|
|
52
|
+
task_id: input.task_id ?? null,
|
|
53
|
+
summary: `记忆沉淀 [${input.type}]: ${input.content.slice(0, 100)}`,
|
|
54
|
+
detail: JSON.stringify({
|
|
55
|
+
tags: input.tags,
|
|
56
|
+
importance,
|
|
57
|
+
project_id: input.project_id,
|
|
58
|
+
expires_at: input.expires_at,
|
|
59
|
+
}),
|
|
60
|
+
});
|
|
61
|
+
return { success: true, memory_id: result.id };
|
|
62
|
+
}
|
|
63
|
+
//# sourceMappingURL=memory-learn.js.map
|
|
@@ -0,0 +1,287 @@
|
|
|
1
|
+
import { readFile, readdir, writeFile } from 'node:fs/promises';
|
|
2
|
+
import { join, relative } from 'node:path';
|
|
3
|
+
import * as yaml from 'yaml';
|
|
4
|
+
/**
|
|
5
|
+
* 召回记忆(替代 memory.search)
|
|
6
|
+
*
|
|
7
|
+
* - person 参数 → 直接读 memory/people/{person}.yaml
|
|
8
|
+
* - project 参数 → 读 memory/projects/{project}/ 下所有 yaml
|
|
9
|
+
* - 无特定 scope → BM25 搜索所有 memory 子目录
|
|
10
|
+
* - 每次召回命中的条目,更新 meta.last_accessed 和 meta.access_count
|
|
11
|
+
*/
|
|
12
|
+
export async function memoryRecall(workspacePath, input) {
|
|
13
|
+
const limit = input.limit ?? 5;
|
|
14
|
+
const depth = input.depth ?? 'summary';
|
|
15
|
+
// 快捷方式:person → scope
|
|
16
|
+
if (input.person) {
|
|
17
|
+
const results = await recallPerson(workspacePath, input.person, depth);
|
|
18
|
+
return { results: results.slice(0, limit) };
|
|
19
|
+
}
|
|
20
|
+
// 快捷方式:project → scope
|
|
21
|
+
if (input.project) {
|
|
22
|
+
const results = await recallProject(workspacePath, input.project, depth, input.query);
|
|
23
|
+
return { results: results.slice(0, limit) };
|
|
24
|
+
}
|
|
25
|
+
// scope 限定搜索
|
|
26
|
+
if (input.scope && input.scope.length > 0) {
|
|
27
|
+
const results = [];
|
|
28
|
+
for (const s of input.scope) {
|
|
29
|
+
const scopeResults = await searchScope(workspacePath, s, input.query, depth);
|
|
30
|
+
results.push(...scopeResults);
|
|
31
|
+
}
|
|
32
|
+
results.sort((a, b) => b.relevance - a.relevance);
|
|
33
|
+
return { results: results.slice(0, limit) };
|
|
34
|
+
}
|
|
35
|
+
// 全局 BM25 搜索
|
|
36
|
+
const results = await globalSearch(workspacePath, input.query, depth);
|
|
37
|
+
results.sort((a, b) => b.relevance - a.relevance);
|
|
38
|
+
return { results: results.slice(0, limit) };
|
|
39
|
+
}
|
|
40
|
+
// ── 按人召回 ──
|
|
41
|
+
async function recallPerson(workspacePath, person, depth) {
|
|
42
|
+
const filePath = join(workspacePath, 'memory', 'people', `${person}.yaml`);
|
|
43
|
+
try {
|
|
44
|
+
const content = await readFile(filePath, 'utf-8');
|
|
45
|
+
const doc = yaml.parse(content);
|
|
46
|
+
if (!doc)
|
|
47
|
+
return [];
|
|
48
|
+
await updateAccessMeta(filePath, content, doc, 'observations');
|
|
49
|
+
if (depth === 'summary') {
|
|
50
|
+
// 返回最近 3 条 observations 的摘要
|
|
51
|
+
const observations = (doc.observations ?? []).slice(-3);
|
|
52
|
+
return observations.map((o) => ({
|
|
53
|
+
content: typeof o === 'string' ? o : (o.content ?? JSON.stringify(o)),
|
|
54
|
+
source: `memory/people/${person}.yaml`,
|
|
55
|
+
relevance: 1.0,
|
|
56
|
+
category: 'people',
|
|
57
|
+
}));
|
|
58
|
+
}
|
|
59
|
+
// full: 返回所有 observations
|
|
60
|
+
return (doc.observations ?? []).map((o) => ({
|
|
61
|
+
content: typeof o === 'string' ? o : (o.content ?? JSON.stringify(o)),
|
|
62
|
+
source: `memory/people/${person}.yaml`,
|
|
63
|
+
relevance: 1.0,
|
|
64
|
+
category: 'people',
|
|
65
|
+
}));
|
|
66
|
+
}
|
|
67
|
+
catch {
|
|
68
|
+
return [];
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
// ── 按项目召回 ──
|
|
72
|
+
async function recallProject(workspacePath, project, depth, query) {
|
|
73
|
+
const projectDir = join(workspacePath, 'memory', 'projects', project);
|
|
74
|
+
const results = [];
|
|
75
|
+
try {
|
|
76
|
+
const files = await findYamlFiles(projectDir);
|
|
77
|
+
for (const file of files) {
|
|
78
|
+
const fileResults = await searchYamlFile(file, workspacePath, query, 'project', depth);
|
|
79
|
+
results.push(...fileResults);
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
catch {
|
|
83
|
+
// 目录不存在
|
|
84
|
+
}
|
|
85
|
+
results.sort((a, b) => b.relevance - a.relevance);
|
|
86
|
+
return results;
|
|
87
|
+
}
|
|
88
|
+
// ── 按 scope 搜索 ──
|
|
89
|
+
async function searchScope(workspacePath, scope, query, depth) {
|
|
90
|
+
const scopeDir = join(workspacePath, 'memory', scope);
|
|
91
|
+
const results = [];
|
|
92
|
+
const category = scope.split('/')[0];
|
|
93
|
+
try {
|
|
94
|
+
const files = await findYamlAndMdFiles(scopeDir);
|
|
95
|
+
for (const file of files) {
|
|
96
|
+
if (file.endsWith('.yaml') || file.endsWith('.yml')) {
|
|
97
|
+
const fileResults = await searchYamlFile(file, workspacePath, query, category, depth);
|
|
98
|
+
results.push(...fileResults);
|
|
99
|
+
}
|
|
100
|
+
else if (file.endsWith('.md')) {
|
|
101
|
+
const fileResults = await searchMdFile(file, workspacePath, query, category);
|
|
102
|
+
results.push(...fileResults);
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
catch {
|
|
107
|
+
// 目录不存在
|
|
108
|
+
}
|
|
109
|
+
return results;
|
|
110
|
+
}
|
|
111
|
+
// ── 全局搜索 ──
|
|
112
|
+
async function globalSearch(workspacePath, query, depth) {
|
|
113
|
+
const results = [];
|
|
114
|
+
const dirs = ['people', 'projects', 'execution', 'commitments', 'self'];
|
|
115
|
+
for (const dir of dirs) {
|
|
116
|
+
const dirPath = join(workspacePath, 'memory', dir);
|
|
117
|
+
try {
|
|
118
|
+
const files = await findYamlAndMdFiles(dirPath);
|
|
119
|
+
for (const file of files) {
|
|
120
|
+
if (file.endsWith('.yaml') || file.endsWith('.yml')) {
|
|
121
|
+
const fileResults = await searchYamlFile(file, workspacePath, query, dir, depth);
|
|
122
|
+
results.push(...fileResults);
|
|
123
|
+
}
|
|
124
|
+
else if (file.endsWith('.md')) {
|
|
125
|
+
const fileResults = await searchMdFile(file, workspacePath, query, dir);
|
|
126
|
+
results.push(...fileResults);
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
catch {
|
|
131
|
+
// 目录不存在,继续
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
return results;
|
|
135
|
+
}
|
|
136
|
+
// ── YAML 文件搜索 ──
|
|
137
|
+
async function searchYamlFile(filePath, workspacePath, query, category, depth) {
|
|
138
|
+
const results = [];
|
|
139
|
+
const relPath = relative(workspacePath, filePath);
|
|
140
|
+
try {
|
|
141
|
+
const content = await readFile(filePath, 'utf-8');
|
|
142
|
+
const doc = yaml.parse(content);
|
|
143
|
+
if (!doc)
|
|
144
|
+
return [];
|
|
145
|
+
// 获取条目列表(可能是 entries、observations 或 active)
|
|
146
|
+
const entries = doc.entries ?? doc.observations ?? doc.active ?? [];
|
|
147
|
+
if (!Array.isArray(entries))
|
|
148
|
+
return [];
|
|
149
|
+
const queryTerms = tokenize(query);
|
|
150
|
+
let hasHit = false;
|
|
151
|
+
for (const entry of entries) {
|
|
152
|
+
const entryContent = typeof entry === 'string' ? entry : (entry.content ?? '');
|
|
153
|
+
const relevance = computeRelevance(entryContent, queryTerms);
|
|
154
|
+
if (relevance > 0) {
|
|
155
|
+
hasHit = true;
|
|
156
|
+
// 检查 heat:heat 为 0 的条目跳过
|
|
157
|
+
if (entry.meta?.heat === 0)
|
|
158
|
+
continue;
|
|
159
|
+
results.push({
|
|
160
|
+
content: depth === 'summary' ? entryContent.slice(0, 200) : entryContent,
|
|
161
|
+
source: relPath,
|
|
162
|
+
relevance,
|
|
163
|
+
category,
|
|
164
|
+
});
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
// 更新 access meta(如果有命中)
|
|
168
|
+
if (hasHit) {
|
|
169
|
+
await updateAccessMeta(filePath, content, doc, doc.entries ? 'entries' : doc.observations ? 'observations' : 'active');
|
|
170
|
+
}
|
|
171
|
+
}
|
|
172
|
+
catch {
|
|
173
|
+
// 文件读取失败
|
|
174
|
+
}
|
|
175
|
+
return results;
|
|
176
|
+
}
|
|
177
|
+
// ── Markdown 文件搜索(向后兼容) ──
|
|
178
|
+
async function searchMdFile(filePath, workspacePath, query, category) {
|
|
179
|
+
const results = [];
|
|
180
|
+
const relPath = relative(workspacePath, filePath);
|
|
181
|
+
try {
|
|
182
|
+
const content = await readFile(filePath, 'utf-8');
|
|
183
|
+
const paragraphs = content.split(/\n(?=##?\s)|\n{2,}/).filter(p => p.trim().length > 20);
|
|
184
|
+
const queryTerms = tokenize(query);
|
|
185
|
+
for (const paragraph of paragraphs) {
|
|
186
|
+
const relevance = computeRelevance(paragraph, queryTerms);
|
|
187
|
+
if (relevance > 0) {
|
|
188
|
+
results.push({
|
|
189
|
+
content: paragraph.trim().slice(0, 200),
|
|
190
|
+
source: relPath,
|
|
191
|
+
relevance,
|
|
192
|
+
category,
|
|
193
|
+
});
|
|
194
|
+
}
|
|
195
|
+
}
|
|
196
|
+
}
|
|
197
|
+
catch {
|
|
198
|
+
// 文件读取失败
|
|
199
|
+
}
|
|
200
|
+
return results;
|
|
201
|
+
}
|
|
202
|
+
// ── 工具函数 ──
|
|
203
|
+
function tokenize(text) {
|
|
204
|
+
const tokens = [];
|
|
205
|
+
const lower = text.toLowerCase();
|
|
206
|
+
// 英文词
|
|
207
|
+
const englishWords = lower.match(/[a-z0-9_]+/g) || [];
|
|
208
|
+
for (const word of englishWords) {
|
|
209
|
+
if (word.length > 1)
|
|
210
|
+
tokens.push(word);
|
|
211
|
+
}
|
|
212
|
+
// 中文 bigram
|
|
213
|
+
const chineseSegments = lower.match(/[\u4e00-\u9fff]+/g) || [];
|
|
214
|
+
for (const segment of chineseSegments) {
|
|
215
|
+
for (const char of segment)
|
|
216
|
+
tokens.push(char);
|
|
217
|
+
for (let i = 0; i < segment.length - 1; i++) {
|
|
218
|
+
tokens.push(segment.slice(i, i + 2));
|
|
219
|
+
}
|
|
220
|
+
}
|
|
221
|
+
return tokens;
|
|
222
|
+
}
|
|
223
|
+
function computeRelevance(text, queryTerms) {
|
|
224
|
+
if (queryTerms.length === 0)
|
|
225
|
+
return 0;
|
|
226
|
+
const lower = text.toLowerCase();
|
|
227
|
+
const textTokens = new Set(tokenize(lower));
|
|
228
|
+
let hits = 0;
|
|
229
|
+
for (const term of queryTerms) {
|
|
230
|
+
if (textTokens.has(term) || lower.includes(term))
|
|
231
|
+
hits++;
|
|
232
|
+
}
|
|
233
|
+
return hits / queryTerms.length;
|
|
234
|
+
}
|
|
235
|
+
async function updateAccessMeta(filePath, _rawContent, doc, listKey) {
|
|
236
|
+
try {
|
|
237
|
+
const entries = doc[listKey];
|
|
238
|
+
if (!Array.isArray(entries))
|
|
239
|
+
return;
|
|
240
|
+
let changed = false;
|
|
241
|
+
const now = new Date().toISOString();
|
|
242
|
+
for (const entry of entries) {
|
|
243
|
+
if (entry?.meta) {
|
|
244
|
+
entry.meta.last_accessed = now;
|
|
245
|
+
entry.meta.access_count = (entry.meta.access_count ?? 0) + 1;
|
|
246
|
+
changed = true;
|
|
247
|
+
}
|
|
248
|
+
}
|
|
249
|
+
if (changed) {
|
|
250
|
+
await writeFile(filePath, yaml.stringify(doc), 'utf-8');
|
|
251
|
+
}
|
|
252
|
+
}
|
|
253
|
+
catch {
|
|
254
|
+
// 更新失败不阻塞
|
|
255
|
+
}
|
|
256
|
+
}
|
|
257
|
+
async function findYamlFiles(dir) {
|
|
258
|
+
const files = [];
|
|
259
|
+
try {
|
|
260
|
+
const entries = await readdir(dir, { withFileTypes: true, recursive: true });
|
|
261
|
+
for (const entry of entries) {
|
|
262
|
+
if (entry.isFile() && (entry.name.endsWith('.yaml') || entry.name.endsWith('.yml'))) {
|
|
263
|
+
files.push(join(entry.parentPath, entry.name));
|
|
264
|
+
}
|
|
265
|
+
}
|
|
266
|
+
}
|
|
267
|
+
catch {
|
|
268
|
+
// 目录不存在
|
|
269
|
+
}
|
|
270
|
+
return files;
|
|
271
|
+
}
|
|
272
|
+
async function findYamlAndMdFiles(dir) {
|
|
273
|
+
const files = [];
|
|
274
|
+
try {
|
|
275
|
+
const entries = await readdir(dir, { withFileTypes: true, recursive: true });
|
|
276
|
+
for (const entry of entries) {
|
|
277
|
+
if (entry.isFile() && (entry.name.endsWith('.yaml') || entry.name.endsWith('.yml') || entry.name.endsWith('.md'))) {
|
|
278
|
+
files.push(join(entry.parentPath, entry.name));
|
|
279
|
+
}
|
|
280
|
+
}
|
|
281
|
+
}
|
|
282
|
+
catch {
|
|
283
|
+
// 目录不存在
|
|
284
|
+
}
|
|
285
|
+
return files;
|
|
286
|
+
}
|
|
287
|
+
//# sourceMappingURL=memory-recall.js.map
|
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
import { getAuditEventsByTask } from '@team-anya/db';
|
|
2
|
+
import { memoryLearn } from './memory-learn.js';
|
|
3
|
+
/**
|
|
4
|
+
* 触发 LLM 反思,从最近事件中提取长期记忆
|
|
5
|
+
*
|
|
6
|
+
* 当前实现:用基于规则的提取代替 LLM 调用。
|
|
7
|
+
* 提取规则:
|
|
8
|
+
* 1. 失败事件 → failure 记忆(高 importance)
|
|
9
|
+
* 2. 决策事件 → decision 记忆
|
|
10
|
+
* 3. 阻塞事件 → insight 记忆
|
|
11
|
+
*
|
|
12
|
+
* 后续升级:调用小模型(Haiku)做更精确的反思提取。
|
|
13
|
+
*/
|
|
14
|
+
export async function memoryReflect(db, workspacePath, input) {
|
|
15
|
+
const entries = [];
|
|
16
|
+
// 收集事件源
|
|
17
|
+
let eventSummaries = [];
|
|
18
|
+
if (input.task_id) {
|
|
19
|
+
const events = getAuditEventsByTask(db, input.task_id);
|
|
20
|
+
eventSummaries = events.map(e => `[${e.event_type}] ${e.summary}`);
|
|
21
|
+
}
|
|
22
|
+
if (input.recent_events) {
|
|
23
|
+
eventSummaries.push(...input.recent_events.split('\n').filter(l => l.trim()));
|
|
24
|
+
}
|
|
25
|
+
if (eventSummaries.length === 0) {
|
|
26
|
+
return { memories_created: 0, entries: [] };
|
|
27
|
+
}
|
|
28
|
+
// 基于规则提取记忆
|
|
29
|
+
for (const summary of eventSummaries) {
|
|
30
|
+
const lower = summary.toLowerCase();
|
|
31
|
+
if (lower.includes('fail') || lower.includes('失败') || lower.includes('error')) {
|
|
32
|
+
entries.push({
|
|
33
|
+
content: `失败记录: ${summary}`,
|
|
34
|
+
type: 'failure',
|
|
35
|
+
tags: ['auto-reflect', 'failure'],
|
|
36
|
+
importance: 0.8,
|
|
37
|
+
});
|
|
38
|
+
}
|
|
39
|
+
else if (lower.includes('decision') || lower.includes('决策')) {
|
|
40
|
+
entries.push({
|
|
41
|
+
content: `决策记录: ${summary}`,
|
|
42
|
+
type: 'decision',
|
|
43
|
+
tags: ['auto-reflect', 'decision'],
|
|
44
|
+
importance: 0.6,
|
|
45
|
+
});
|
|
46
|
+
}
|
|
47
|
+
else if (lower.includes('block') || lower.includes('阻塞') || lower.includes('卡住')) {
|
|
48
|
+
entries.push({
|
|
49
|
+
content: `阻塞经验: ${summary}`,
|
|
50
|
+
type: 'insight',
|
|
51
|
+
tags: ['auto-reflect', 'blocker'],
|
|
52
|
+
importance: 0.7,
|
|
53
|
+
});
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
// 去重(避免同一事件重复写入)
|
|
57
|
+
const seen = new Set();
|
|
58
|
+
const uniqueEntries = entries.filter(e => {
|
|
59
|
+
const key = `${e.type}:${e.content.slice(0, 50)}`;
|
|
60
|
+
if (seen.has(key))
|
|
61
|
+
return false;
|
|
62
|
+
seen.add(key);
|
|
63
|
+
return true;
|
|
64
|
+
});
|
|
65
|
+
// 写入记忆
|
|
66
|
+
for (const entry of uniqueEntries) {
|
|
67
|
+
await memoryLearn(db, workspacePath, {
|
|
68
|
+
content: entry.content,
|
|
69
|
+
type: entry.type,
|
|
70
|
+
tags: entry.tags,
|
|
71
|
+
importance: entry.importance,
|
|
72
|
+
task_id: input.task_id,
|
|
73
|
+
});
|
|
74
|
+
}
|
|
75
|
+
return {
|
|
76
|
+
memories_created: uniqueEntries.length,
|
|
77
|
+
entries: uniqueEntries,
|
|
78
|
+
};
|
|
79
|
+
}
|
|
80
|
+
//# sourceMappingURL=memory-reflect.js.map
|
|
@@ -0,0 +1,119 @@
|
|
|
1
|
+
import { insertAuditEvent } from '@team-anya/db';
|
|
2
|
+
import { readFile, writeFile, mkdir } from 'node:fs/promises';
|
|
3
|
+
import { join, dirname } from 'node:path';
|
|
4
|
+
import * as yaml from 'yaml';
|
|
5
|
+
// 按 category 自动设置默认衰减率
|
|
6
|
+
const DEFAULT_DECAY_RATES = {
|
|
7
|
+
people: 0.01, // 人的记忆衰减很慢
|
|
8
|
+
project: 0.05, // 项目经验中等衰减
|
|
9
|
+
execution: 0.1, // 执行经验衰减较快
|
|
10
|
+
commitment: 0.2, // 承诺衰减快(需要定期刷新)
|
|
11
|
+
self: 0.02, // 自我认知衰减很慢
|
|
12
|
+
};
|
|
13
|
+
/**
|
|
14
|
+
* 写入记忆(替代 memory.learn)
|
|
15
|
+
*
|
|
16
|
+
* 按 category + target 映射到 YAML 文件路径,支持追加和创建。
|
|
17
|
+
* 每条记忆带 meta(created_at, heat, decay_rate, access_count, last_accessed)。
|
|
18
|
+
*/
|
|
19
|
+
export async function memoryRemember(db, workspacePath, input) {
|
|
20
|
+
const importance = input.importance ?? 0.5;
|
|
21
|
+
if (importance < 0 || importance > 1) {
|
|
22
|
+
throw new Error('importance 必须在 0-1 之间');
|
|
23
|
+
}
|
|
24
|
+
const filePath = resolveFilePath(workspacePath, input);
|
|
25
|
+
await mkdir(dirname(filePath), { recursive: true });
|
|
26
|
+
const now = new Date().toISOString();
|
|
27
|
+
const newEntry = {
|
|
28
|
+
content: input.content,
|
|
29
|
+
meta: {
|
|
30
|
+
created_at: now,
|
|
31
|
+
heat: 1.0,
|
|
32
|
+
decay_rate: DEFAULT_DECAY_RATES[input.category] ?? 0.05,
|
|
33
|
+
access_count: 0,
|
|
34
|
+
last_accessed: now,
|
|
35
|
+
importance,
|
|
36
|
+
...(input.source ? { source: input.source } : {}),
|
|
37
|
+
...(input.expires_at ? { expires_at: input.expires_at } : {}),
|
|
38
|
+
},
|
|
39
|
+
};
|
|
40
|
+
let entries = [];
|
|
41
|
+
let doc = {};
|
|
42
|
+
// 读取已有文件
|
|
43
|
+
try {
|
|
44
|
+
const existing = await readFile(filePath, 'utf-8');
|
|
45
|
+
doc = yaml.parse(existing) ?? {};
|
|
46
|
+
}
|
|
47
|
+
catch {
|
|
48
|
+
// 文件不存在,创建新的
|
|
49
|
+
}
|
|
50
|
+
if (input.category === 'people') {
|
|
51
|
+
// people 类型特殊处理:追加到 observations 列表
|
|
52
|
+
if (!doc.id)
|
|
53
|
+
doc.id = input.target;
|
|
54
|
+
if (!doc.observations)
|
|
55
|
+
doc.observations = [];
|
|
56
|
+
doc.observations.push(newEntry);
|
|
57
|
+
entries = doc.observations;
|
|
58
|
+
}
|
|
59
|
+
else if (input.category === 'commitment') {
|
|
60
|
+
// commitment:追加到 active 列表
|
|
61
|
+
if (!doc.active)
|
|
62
|
+
doc.active = [];
|
|
63
|
+
doc.active.push(newEntry);
|
|
64
|
+
entries = doc.active;
|
|
65
|
+
}
|
|
66
|
+
else {
|
|
67
|
+
// 其他类型:追加到 entries 列表
|
|
68
|
+
if (!doc.entries)
|
|
69
|
+
doc.entries = [];
|
|
70
|
+
doc.entries.push(newEntry);
|
|
71
|
+
entries = doc.entries;
|
|
72
|
+
}
|
|
73
|
+
await writeFile(filePath, yaml.stringify(doc), 'utf-8');
|
|
74
|
+
// 写审计事件
|
|
75
|
+
try {
|
|
76
|
+
insertAuditEvent(db, {
|
|
77
|
+
event_type: 'memory_remembered',
|
|
78
|
+
actor: 'system',
|
|
79
|
+
task_id: input.source ?? null,
|
|
80
|
+
summary: `记忆写入 [${input.category}${input.target ? '/' + input.target : ''}]: ${input.content.slice(0, 100)}`,
|
|
81
|
+
detail: JSON.stringify({
|
|
82
|
+
category: input.category,
|
|
83
|
+
target: input.target,
|
|
84
|
+
sub_type: input.sub_type,
|
|
85
|
+
importance,
|
|
86
|
+
}),
|
|
87
|
+
});
|
|
88
|
+
}
|
|
89
|
+
catch {
|
|
90
|
+
// 审计失败不阻塞主流程
|
|
91
|
+
}
|
|
92
|
+
const relPath = filePath.replace(workspacePath + '/', '');
|
|
93
|
+
return { success: true, path: relPath, entry_count: entries.length };
|
|
94
|
+
}
|
|
95
|
+
/**
|
|
96
|
+
* 根据 category + target 映射到文件路径
|
|
97
|
+
*/
|
|
98
|
+
function resolveFilePath(workspacePath, input) {
|
|
99
|
+
const memoryBase = join(workspacePath, 'memory');
|
|
100
|
+
switch (input.category) {
|
|
101
|
+
case 'people':
|
|
102
|
+
if (!input.target)
|
|
103
|
+
throw new Error('people 类型必须指定 target (sender_id)');
|
|
104
|
+
return join(memoryBase, 'people', `${input.target}.yaml`);
|
|
105
|
+
case 'project':
|
|
106
|
+
if (!input.target)
|
|
107
|
+
throw new Error('project 类型必须指定 target (project_id)');
|
|
108
|
+
return join(memoryBase, 'projects', input.target, `${input.sub_type || 'general'}.yaml`);
|
|
109
|
+
case 'execution':
|
|
110
|
+
return join(memoryBase, 'execution', `${input.sub_type || 'general'}.yaml`);
|
|
111
|
+
case 'commitment':
|
|
112
|
+
return join(memoryBase, 'commitments', 'active.yaml');
|
|
113
|
+
case 'self':
|
|
114
|
+
return join(memoryBase, 'self', 'profile.yaml');
|
|
115
|
+
default:
|
|
116
|
+
throw new Error(`未知记忆类别: ${input.category}`);
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
//# sourceMappingURL=memory-remember.js.map
|