metame-cli 1.5.19 → 1.5.21
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/index.js +157 -80
- package/package.json +2 -2
- package/scripts/bin/bootstrap-worktree.sh +20 -0
- package/scripts/core/audit.js +190 -0
- package/scripts/core/handoff.js +780 -0
- package/scripts/core/handoff.test.js +1074 -0
- package/scripts/core/memory-model.js +183 -0
- package/scripts/core/memory-model.test.js +486 -0
- package/scripts/core/reactive-paths.js +44 -0
- package/scripts/core/reactive-paths.test.js +35 -0
- package/scripts/core/reactive-prompt.js +51 -0
- package/scripts/core/reactive-prompt.test.js +88 -0
- package/scripts/core/reactive-signal.js +40 -0
- package/scripts/core/reactive-signal.test.js +88 -0
- package/scripts/core/thread-chat-id.js +52 -0
- package/scripts/core/thread-chat-id.test.js +113 -0
- package/scripts/daemon-bridges.js +92 -38
- package/scripts/daemon-claude-engine.js +373 -444
- package/scripts/daemon-command-router.js +82 -8
- package/scripts/daemon-engine-runtime.js +7 -10
- package/scripts/daemon-reactive-lifecycle.js +100 -33
- package/scripts/daemon-session-commands.js +133 -43
- package/scripts/daemon-session-store.js +300 -82
- package/scripts/daemon-team-dispatch.js +16 -16
- package/scripts/daemon.js +21 -175
- package/scripts/deploy-manifest.js +90 -0
- package/scripts/docs/maintenance-manual.md +14 -11
- package/scripts/docs/pointer-map.md +13 -4
- package/scripts/feishu-adapter.js +31 -27
- package/scripts/hooks/intent-engine.js +6 -3
- package/scripts/hooks/intent-memory-recall.js +1 -0
- package/scripts/hooks/intent-perpetual.js +1 -1
- package/scripts/memory-extract.js +5 -97
- package/scripts/memory-gc.js +35 -90
- package/scripts/memory-migrate-v2.js +304 -0
- package/scripts/memory-nightly-reflect.js +40 -41
- package/scripts/memory.js +340 -859
- package/scripts/migrate-reactive-paths.js +122 -0
- package/scripts/signal-capture.js +4 -0
- package/scripts/sync-plugin.js +56 -0
|
@@ -33,7 +33,7 @@ const MIN_SEARCH_COUNT = 3;
|
|
|
33
33
|
const WINDOW_DAYS = 7;
|
|
34
34
|
const MAX_FACTS = 20;
|
|
35
35
|
const LOCK_TIMEOUT_MS = 10 * 60 * 1000; // 10 minutes
|
|
36
|
-
const
|
|
36
|
+
const EXCLUDED_KINDS = ['project_milestone', 'synthesized_insight', 'knowledge_capsule', 'bug_lesson'];
|
|
37
37
|
|
|
38
38
|
// Ensure output directories exist at startup
|
|
39
39
|
[MEMORY_DIR, DECISIONS_DIR, LESSONS_DIR, CAPSULES_DIR].forEach(d => fs.mkdirSync(d, { recursive: true }));
|
|
@@ -109,19 +109,18 @@ function writeReflectLog(record) {
|
|
|
109
109
|
* Returns array of plain objects.
|
|
110
110
|
*/
|
|
111
111
|
function queryHotFacts(db, windowDays = WINDOW_DAYS) {
|
|
112
|
-
const
|
|
112
|
+
const kindPlaceholders = EXCLUDED_KINDS.map(() => '?').join(', ');
|
|
113
113
|
const stmt = db.prepare(`
|
|
114
|
-
SELECT id,
|
|
115
|
-
FROM
|
|
114
|
+
SELECT id, title, kind, content, confidence, search_count, created_at
|
|
115
|
+
FROM memory_items
|
|
116
116
|
WHERE search_count >= ${MIN_SEARCH_COUNT}
|
|
117
117
|
AND created_at >= datetime('now', '-${windowDays} days')
|
|
118
|
-
AND
|
|
119
|
-
AND
|
|
120
|
-
AND relation NOT IN (${relationPlaceholders})
|
|
118
|
+
AND state = 'active'
|
|
119
|
+
AND kind NOT IN (${kindPlaceholders})
|
|
121
120
|
ORDER BY search_count DESC, created_at DESC
|
|
122
121
|
LIMIT ${MAX_FACTS}
|
|
123
122
|
`);
|
|
124
|
-
return stmt.all(...
|
|
123
|
+
return stmt.all(...EXCLUDED_KINDS);
|
|
125
124
|
}
|
|
126
125
|
|
|
127
126
|
/**
|
|
@@ -194,7 +193,7 @@ function entityPrefix(entity) {
|
|
|
194
193
|
function collectCapsuleGroups(facts, minGroupSize = 3) {
|
|
195
194
|
const groups = new Map();
|
|
196
195
|
for (const fact of Array.isArray(facts) ? facts : []) {
|
|
197
|
-
const prefix = entityPrefix(fact && fact.
|
|
196
|
+
const prefix = entityPrefix(fact && fact.title);
|
|
198
197
|
if (!prefix) continue;
|
|
199
198
|
if (!groups.has(prefix)) groups.set(prefix, []);
|
|
200
199
|
groups.get(prefix).push(fact);
|
|
@@ -304,9 +303,9 @@ async function run() {
|
|
|
304
303
|
|
|
305
304
|
const factsJson = JSON.stringify(
|
|
306
305
|
hotFacts.map(f => ({
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
306
|
+
title: f.title,
|
|
307
|
+
kind: f.kind,
|
|
308
|
+
content: f.content,
|
|
310
309
|
confidence: f.confidence,
|
|
311
310
|
search_count: f.search_count,
|
|
312
311
|
})),
|
|
@@ -406,9 +405,9 @@ Rules:
|
|
|
406
405
|
|
|
407
406
|
const sourceItems = factsForGroup ? factsForGroup.items : group.items;
|
|
408
407
|
const groupFacts = sourceItems.map(f => ({
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
408
|
+
title: f.title,
|
|
409
|
+
kind: f.kind,
|
|
410
|
+
content: f.content,
|
|
412
411
|
search_count: f.search_count,
|
|
413
412
|
}));
|
|
414
413
|
const capsulePrompt = playbookExists
|
|
@@ -504,15 +503,15 @@ entity_prefix: ${group.prefix}
|
|
|
504
503
|
}
|
|
505
504
|
|
|
506
505
|
// ── Conflict Resolution ──────────────────────────────────────────────
|
|
507
|
-
// Query CONFLICT
|
|
508
|
-
// Loser is
|
|
506
|
+
// Query CONFLICT memory_items grouped by title+kind, ask Haiku to pick winner.
|
|
507
|
+
// Loser is archived with supersedes_id; winner restored to active.
|
|
509
508
|
let conflictsResolved = 0;
|
|
510
509
|
try {
|
|
511
510
|
const conflictGroups = db.prepare(`
|
|
512
|
-
SELECT
|
|
513
|
-
FROM
|
|
514
|
-
WHERE
|
|
515
|
-
GROUP BY
|
|
511
|
+
SELECT title, kind, COUNT(*) as cnt
|
|
512
|
+
FROM memory_items
|
|
513
|
+
WHERE state = 'conflict'
|
|
514
|
+
GROUP BY title, kind
|
|
516
515
|
HAVING cnt >= 2
|
|
517
516
|
ORDER BY cnt DESC
|
|
518
517
|
LIMIT 10
|
|
@@ -525,23 +524,23 @@ entity_prefix: ${group.prefix}
|
|
|
525
524
|
const allConflicts = [];
|
|
526
525
|
for (const g of conflictGroups) {
|
|
527
526
|
const rows = db.prepare(`
|
|
528
|
-
SELECT id,
|
|
529
|
-
FROM
|
|
530
|
-
WHERE
|
|
527
|
+
SELECT id, title, kind, content, confidence, created_at
|
|
528
|
+
FROM memory_items
|
|
529
|
+
WHERE title = ? AND kind = ? AND state = 'conflict'
|
|
531
530
|
ORDER BY created_at DESC
|
|
532
|
-
`).all(g.
|
|
533
|
-
if (rows.length >= 2) allConflicts.push({
|
|
531
|
+
`).all(g.title, g.kind);
|
|
532
|
+
if (rows.length >= 2) allConflicts.push({ title: g.title, kind: g.kind, facts: rows });
|
|
534
533
|
}
|
|
535
534
|
|
|
536
535
|
if (allConflicts.length > 0) {
|
|
537
536
|
// Limit to 5 groups to avoid truncating serialized JSON
|
|
538
537
|
const conflictInput = allConflicts.slice(0, 5).map(g => ({
|
|
539
|
-
|
|
540
|
-
|
|
541
|
-
candidates: g.facts.slice(0, 5).map(f => ({ id: f.id,
|
|
538
|
+
title: g.title,
|
|
539
|
+
kind: g.kind,
|
|
540
|
+
candidates: g.facts.slice(0, 5).map(f => ({ id: f.id, content: f.content.slice(0, 150), confidence: f.confidence, created_at: f.created_at })),
|
|
542
541
|
}));
|
|
543
542
|
|
|
544
|
-
const resolvePrompt = `你是知识库冲突调解员。以下是同一
|
|
543
|
+
const resolvePrompt = `你是知识库冲突调解员。以下是同一 title+kind 下的冲突记忆条目,请选出每组最准确的一条保留。
|
|
545
544
|
|
|
546
545
|
冲突组(JSON):
|
|
547
546
|
${JSON.stringify(conflictInput, null, 2)}
|
|
@@ -549,15 +548,15 @@ ${JSON.stringify(conflictInput, null, 2)}
|
|
|
549
548
|
输出 JSON 数组,每个元素对应一组冲突的裁决:
|
|
550
549
|
[
|
|
551
550
|
{
|
|
552
|
-
"
|
|
553
|
-
"
|
|
554
|
-
"winner_id": "保留的
|
|
551
|
+
"title": "...",
|
|
552
|
+
"kind": "...",
|
|
553
|
+
"winner_id": "保留的item id",
|
|
555
554
|
"reason": "一句话理由"
|
|
556
555
|
}
|
|
557
556
|
]
|
|
558
557
|
|
|
559
558
|
规则:
|
|
560
|
-
- 优先选最新(created_at)且 confidence
|
|
559
|
+
- 优先选最新(created_at)且 confidence 高的
|
|
561
560
|
- 如果旧条目更准确具体,选旧的
|
|
562
561
|
- 只输出 JSON 数组`;
|
|
563
562
|
|
|
@@ -569,9 +568,9 @@ ${JSON.stringify(conflictInput, null, 2)}
|
|
|
569
568
|
const verdicts = parseJsonFromLlm(resolveRaw);
|
|
570
569
|
if (Array.isArray(verdicts)) {
|
|
571
570
|
for (const v of verdicts) {
|
|
572
|
-
if (!v || !v.winner_id || !v.
|
|
571
|
+
if (!v || !v.winner_id || !v.title || !v.kind) continue;
|
|
573
572
|
// Validate winner exists in our conflict set
|
|
574
|
-
const group = allConflicts.find(g => g.
|
|
573
|
+
const group = allConflicts.find(g => g.title === v.title && g.kind === v.kind);
|
|
575
574
|
if (!group) continue;
|
|
576
575
|
const winnerExists = group.facts.some(f => f.id === v.winner_id);
|
|
577
576
|
if (!winnerExists) continue;
|
|
@@ -579,16 +578,16 @@ ${JSON.stringify(conflictInput, null, 2)}
|
|
|
579
578
|
const loserIds = group.facts.filter(f => f.id !== v.winner_id).map(f => f.id);
|
|
580
579
|
if (loserIds.length === 0) continue;
|
|
581
580
|
|
|
582
|
-
// Mark losers as superseded
|
|
581
|
+
// Mark losers as archived (superseded by winner)
|
|
583
582
|
const placeholders = loserIds.map(() => '?').join(',');
|
|
584
583
|
db.prepare(
|
|
585
|
-
`UPDATE
|
|
584
|
+
`UPDATE memory_items SET supersedes_id = ?, state = 'archived', updated_at = datetime('now')
|
|
586
585
|
WHERE id IN (${placeholders})`
|
|
587
586
|
).run(v.winner_id, ...loserIds);
|
|
588
587
|
|
|
589
|
-
// Restore winner
|
|
588
|
+
// Restore winner to active
|
|
590
589
|
db.prepare(
|
|
591
|
-
`UPDATE
|
|
590
|
+
`UPDATE memory_items SET state = 'active', updated_at = datetime('now') WHERE id = ?`
|
|
592
591
|
).run(v.winner_id);
|
|
593
592
|
|
|
594
593
|
conflictsResolved += loserIds.length;
|
|
@@ -652,6 +651,6 @@ module.exports = {
|
|
|
652
651
|
collectCapsuleGroups,
|
|
653
652
|
entityPrefix,
|
|
654
653
|
parseJsonFromLlm,
|
|
655
|
-
|
|
654
|
+
EXCLUDED_KINDS,
|
|
656
655
|
},
|
|
657
656
|
};
|