newtype-profile 1.0.47 → 1.0.48
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 +10 -5
- package/README.zh-cn.md +10 -5
- package/dist/cli/index.js +1 -1
- package/dist/features/builtin-commands/templates/memory-consolidate.d.ts +1 -1
- package/dist/hooks/memory-system/constants.d.ts +4 -2
- package/dist/hooks/memory-system/storage.d.ts +26 -2
- package/dist/hooks/memory-system/types.d.ts +6 -0
- package/dist/index.js +305 -22
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -329,18 +329,23 @@ newtype-profile includes an automatic memory system for cross-session knowledge
|
|
|
329
329
|
### How It Works
|
|
330
330
|
|
|
331
331
|
1. **Auto-save**: When a conversation ends (session.idle), key information is extracted and saved to `.opencode/memory/YYYY-MM-DD.md`
|
|
332
|
-
2. **
|
|
333
|
-
3. **
|
|
332
|
+
2. **Full transcript**: A complete conversation log is stored in `.opencode/memory/full/<sessionID>.md` (overwrites per session)
|
|
333
|
+
3. **Auto-archive**: Logs older than 7 days are consolidated into `.opencode/MEMORY.md`
|
|
334
|
+
4. **Deep summaries**: If a daily entry includes Decisions/TODOs or tags (#project/#preference/#policy/#important), the archivist agent summarizes the full transcript in background
|
|
335
|
+
5. **AI Awareness**: Chief knows about the memory system and can query it when needed
|
|
334
336
|
|
|
335
337
|
### File Structure
|
|
336
338
|
|
|
337
339
|
```
|
|
338
340
|
your-project/
|
|
339
341
|
└── .opencode/
|
|
340
|
-
├── MEMORY.md # Long-term memory (archived)
|
|
342
|
+
├── MEMORY.md # Long-term memory (archived + deep summaries)
|
|
341
343
|
└── memory/
|
|
342
|
-
├── 2026-01-29.md #
|
|
343
|
-
├── 2026-01-28.md #
|
|
344
|
+
├── 2026-01-29.md # Daily summaries
|
|
345
|
+
├── 2026-01-28.md # Daily summaries
|
|
346
|
+
├── full/
|
|
347
|
+
│ ├── ses_xxxx.md # Full transcript per session
|
|
348
|
+
│ └── ...
|
|
344
349
|
└── ...
|
|
345
350
|
```
|
|
346
351
|
|
package/README.zh-cn.md
CHANGED
|
@@ -329,18 +329,23 @@ newtype-profile 内置了跨会话记忆系统,自动保存重要信息:
|
|
|
329
329
|
### 工作原理
|
|
330
330
|
|
|
331
331
|
1. **自动保存**:对话结束时(session.idle),关键信息被提取并保存到 `.opencode/memory/YYYY-MM-DD.md`
|
|
332
|
-
2.
|
|
333
|
-
3.
|
|
332
|
+
2. **完整对话**:每个 session 的全文日志保存到 `.opencode/memory/full/<sessionID>.md`(覆盖写)
|
|
333
|
+
3. **自动归档**:超过 7 天的日志自动合并到 `.opencode/MEMORY.md`
|
|
334
|
+
4. **深度摘要**:当日记含 Decisions/TODOs 或标签(#project/#preference/#policy/#important)时,archivist 会后台读取全文生成深度摘要
|
|
335
|
+
5. **AI 感知**:Chief 知道记忆系统的存在,需要时会主动查询
|
|
334
336
|
|
|
335
337
|
### 文件结构
|
|
336
338
|
|
|
337
339
|
```
|
|
338
340
|
你的项目/
|
|
339
341
|
└── .opencode/
|
|
340
|
-
├── MEMORY.md #
|
|
342
|
+
├── MEMORY.md # 长期记忆(含深度摘要)
|
|
341
343
|
└── memory/
|
|
342
|
-
├── 2026-01-29.md #
|
|
343
|
-
├── 2026-01-28.md #
|
|
344
|
+
├── 2026-01-29.md # 每日摘要
|
|
345
|
+
├── 2026-01-28.md # 每日摘要
|
|
346
|
+
├── full/
|
|
347
|
+
│ ├── ses_xxxx.md # 每个 session 的完整记录
|
|
348
|
+
│ └── ...
|
|
344
349
|
└── ...
|
|
345
350
|
```
|
|
346
351
|
|
package/dist/cli/index.js
CHANGED
|
@@ -2253,7 +2253,7 @@ var require_picocolors = __commonJS((exports, module) => {
|
|
|
2253
2253
|
var require_package = __commonJS((exports, module) => {
|
|
2254
2254
|
module.exports = {
|
|
2255
2255
|
name: "newtype-profile",
|
|
2256
|
-
version: "1.0.
|
|
2256
|
+
version: "1.0.48",
|
|
2257
2257
|
description: "AI Agent Collaboration System for Content Creation - Based on oh-my-opencode",
|
|
2258
2258
|
main: "dist/index.js",
|
|
2259
2259
|
types: "dist/index.d.ts",
|
|
@@ -1 +1 @@
|
|
|
1
|
-
export declare const MEMORY_CONSOLIDATE_TEMPLATE = "Consolidate daily memory logs into long-term memory.\n\n## USAGE\n\n`/memory-consolidate`\n\n## WHAT TO DO\n\nRead all files in `.opencode/memory/` directory and consolidate important information into `.opencode/MEMORY.md`.\n\n### Step 1: Read Daily Logs\n\nUse `glob` to find all memory files:\n```\nglob(\".opencode/memory/*.md\")\n```\n\nThen `read` each file to understand the contents.\n\n### Step 2: Extract Key Information\n\nFrom the daily logs, identify:\n1. **User Preferences** - Recurring patterns in how user likes things done\n2. **Important Decisions** - Choices made that affect future work\n3. **Project Milestones** - Significant completions or achievements\n4. **Recurring Problems** - Issues that came up multiple times and their solutions\n5. **Key Insights** - Valuable learnings worth preserving\n\n### Step 3: Update MEMORY.md\n\nRead existing `.opencode/MEMORY.md` (create if doesn't exist).\n\nAppend new consolidated information using this structure:\n\n```markdown\n## Consolidated: YYYY-MM-DD\n\n### User Preferences\n- [preference 1]\n- [preference 2]\n\n### Decisions Made\n- [decision with context]\n\n### Lessons Learned\n- [insight or lesson]\n\n---\n```\n\n### Step 4: Archive Processed Logs\n\nAfter consolidation, you may suggest to the user whether to:\n- Keep the daily logs for reference\n- Delete processed daily logs to save space\n\n## OUTPUT FORMAT\n\nAfter consolidation:\n```\n\u2705 Memory consolidated\n\n**Processed:** X daily log files\n**Extracted:**\n- Y user preferences\n- Z decisions\n- W insights\n\n**Updated:** .opencode/MEMORY.md\n\n\uD83D\uDCA1 Tip: Daily logs in .opencode/memory/ are preserved. \n Delete them manually if you want to save space.\n```\n\nIf no memory files found:\n```\n\uD83D\uDCED No memory files to consolidate\n\nMemory files are automatically created after conversations.\nCheck back after a few chat sessions.\n```\n\n## IMPORTANT\n\n- Never delete information from MEMORY.md, only append\n- Preserve existing structure and content\n- Be selective - only consolidate truly important information\n- Use user's language (Chinese/English) based on existing content\n";
|
|
1
|
+
export declare const MEMORY_CONSOLIDATE_TEMPLATE = "Consolidate daily memory logs into long-term memory.\n\n## USAGE\n\n`/memory-consolidate`\n\n## WHAT TO DO\n\nRead all files in `.opencode/memory/` directory and consolidate important information into `.opencode/MEMORY.md`.\nIf a daily log entry contains Decisions, TODOs, or critical tags (#project, #preference, #policy, #important),\nalso pull the full transcript from `.opencode/memory/full/<sessionID>.md` for deeper summarization.\n\n### Step 1: Read Daily Logs\n\nUse `glob` to find all memory files:\n```\nglob(\".opencode/memory/*.md\")\n```\n\nThen `read` each file to understand the contents.\n\n### Step 2: Extract Key Information\n\nFrom the daily logs, identify:\n1. **User Preferences** - Recurring patterns in how user likes things done\n2. **Important Decisions** - Choices made that affect future work\n3. **Project Milestones** - Significant completions or achievements\n4. **Recurring Problems** - Issues that came up multiple times and their solutions\n5. **Key Insights** - Valuable learnings worth preserving\n\nIf deep-summary trigger conditions are met, read the full transcript for that session and extract additional\npreferences, decisions, and lessons.\n\n### Step 3: Update MEMORY.md\n\nRead existing `.opencode/MEMORY.md` (create if doesn't exist).\n\nAppend new consolidated information using this structure:\n\n```markdown\n## Consolidated: YYYY-MM-DD\n\n### User Preferences\n- [preference 1]\n- [preference 2]\n\n### Decisions Made\n- [decision with context]\n\n### Lessons Learned\n- [insight or lesson]\n\n### Deep Summaries (when triggered)\n- [sessionID] preference/decision/lesson from full transcript\n\n---\n```\n\n### Step 4: Archive Processed Logs\n\nAfter consolidation, you may suggest to the user whether to:\n- Keep the daily logs for reference\n- Delete processed daily logs to save space\n\n## OUTPUT FORMAT\n\nAfter consolidation:\n```\n\u2705 Memory consolidated\n\n**Processed:** X daily log files\n**Extracted:**\n- Y user preferences\n- Z decisions\n- W insights\n\n**Updated:** .opencode/MEMORY.md\n\n\uD83D\uDCA1 Tip: Daily logs in .opencode/memory/ are preserved. \n Delete them manually if you want to save space.\n```\n\nIf no memory files found:\n```\n\uD83D\uDCED No memory files to consolidate\n\nMemory files are automatically created after conversations.\nCheck back after a few chat sessions.\n```\n\n## IMPORTANT\n\n- Never delete information from MEMORY.md, only append\n- Preserve existing structure and content\n- Be selective - only consolidate truly important information\n- Use user's language (Chinese/English) based on existing content\n";
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
export declare const HOOK_NAME = "memory-system";
|
|
2
|
-
/** Minimum number of messages to trigger memory save */
|
|
3
|
-
export declare const MIN_MESSAGES_TO_SAVE = 4;
|
|
4
2
|
/** Memory storage directory relative to project root */
|
|
5
3
|
export declare const MEMORY_DIR = ".opencode/memory";
|
|
4
|
+
/** Full transcript storage directory relative to project root */
|
|
5
|
+
export declare const FULL_MEMORY_DIR = ".opencode/memory/full";
|
|
6
6
|
/** Long-term memory file */
|
|
7
7
|
export declare const MEMORY_FILE = ".opencode/MEMORY.md";
|
|
8
8
|
/** Grace period before saving (ms) - allows for quick follow-ups */
|
|
@@ -11,3 +11,5 @@ export declare const SAVE_GRACE_PERIOD_MS = 5000;
|
|
|
11
11
|
export declare const MAX_SUMMARY_LENGTH = 2000;
|
|
12
12
|
/** Days after which daily logs are auto-archived to MEMORY.md */
|
|
13
13
|
export declare const ARCHIVE_AFTER_DAYS = 7;
|
|
14
|
+
/** Tags that trigger deep summary from full transcripts */
|
|
15
|
+
export declare const DEEP_SUMMARY_TAGS: readonly ["#project", "#preference", "#policy", "#important"];
|
|
@@ -1,10 +1,34 @@
|
|
|
1
|
-
import type { MemoryEntry } from "./types";
|
|
1
|
+
import type { MemoryEntry, MemoryEntryMessage } from "./types";
|
|
2
2
|
export interface ArchiveResult {
|
|
3
3
|
archived: string[];
|
|
4
4
|
totalFiles: number;
|
|
5
5
|
needsArchive: boolean;
|
|
6
6
|
}
|
|
7
|
+
export interface FullTranscriptBlock {
|
|
8
|
+
role: string;
|
|
9
|
+
content: string;
|
|
10
|
+
}
|
|
11
|
+
export interface MemorySummary {
|
|
12
|
+
userPreferences: string[];
|
|
13
|
+
decisions: string[];
|
|
14
|
+
lessons: string[];
|
|
15
|
+
}
|
|
16
|
+
export interface DailyLogSession {
|
|
17
|
+
sessionID?: string;
|
|
18
|
+
raw: string;
|
|
19
|
+
tags: string[];
|
|
20
|
+
decisions: string[];
|
|
21
|
+
todos: string[];
|
|
22
|
+
}
|
|
7
23
|
export declare function checkArchiveNeeded(projectDir: string): ArchiveResult;
|
|
8
|
-
export declare function archiveOldMemories(projectDir: string
|
|
24
|
+
export declare function archiveOldMemories(projectDir: string, options?: {
|
|
25
|
+
deepSummarizer?: (session: DailyLogSession, fullContent: string) => Promise<string | null>;
|
|
26
|
+
}): Promise<ArchiveResult>;
|
|
27
|
+
export declare function parseDailyLogSessions(content: string): DailyLogSession[];
|
|
28
|
+
export declare function shouldDeepSummarize(session: DailyLogSession): boolean;
|
|
29
|
+
export declare function getFullTranscriptPath(projectDir: string, sessionID: string): string;
|
|
30
|
+
export declare function parseFullTranscript(content: string): FullTranscriptBlock[];
|
|
31
|
+
export declare function summarizeFullTranscript(content: string): MemorySummary;
|
|
9
32
|
export declare function appendMemoryEntry(projectDir: string, entry: MemoryEntry): boolean;
|
|
10
33
|
export declare function hasMemoryForSession(projectDir: string, sessionID: string): boolean;
|
|
34
|
+
export declare function saveFullTranscript(projectDir: string, sessionID: string, messages: MemoryEntryMessage[]): boolean;
|
|
@@ -5,6 +5,12 @@ export interface MemoryEntry {
|
|
|
5
5
|
keyPoints: string[];
|
|
6
6
|
decisions?: string[];
|
|
7
7
|
todos?: string[];
|
|
8
|
+
tags?: string[];
|
|
9
|
+
}
|
|
10
|
+
export interface MemoryEntryMessage {
|
|
11
|
+
role: string;
|
|
12
|
+
text: string;
|
|
13
|
+
timestamp?: string;
|
|
8
14
|
}
|
|
9
15
|
export interface SessionState {
|
|
10
16
|
saved: boolean;
|
package/dist/index.js
CHANGED
|
@@ -24326,15 +24326,24 @@ ${buildStandaloneVerificationReminder(subagentSessionId)}
|
|
|
24326
24326
|
}
|
|
24327
24327
|
// src/hooks/memory-system/constants.ts
|
|
24328
24328
|
var HOOK_NAME6 = "memory-system";
|
|
24329
|
-
var MIN_MESSAGES_TO_SAVE = 4;
|
|
24330
24329
|
var MEMORY_DIR = ".opencode/memory";
|
|
24330
|
+
var FULL_MEMORY_DIR = ".opencode/memory/full";
|
|
24331
24331
|
var MEMORY_FILE = ".opencode/MEMORY.md";
|
|
24332
24332
|
var SAVE_GRACE_PERIOD_MS = 5000;
|
|
24333
24333
|
var MAX_SUMMARY_LENGTH = 2000;
|
|
24334
24334
|
var ARCHIVE_AFTER_DAYS = 7;
|
|
24335
|
+
var DEEP_SUMMARY_TAGS = ["#project", "#preference", "#policy", "#important"];
|
|
24335
24336
|
|
|
24336
24337
|
// src/hooks/memory-system/storage.ts
|
|
24337
|
-
import {
|
|
24338
|
+
import {
|
|
24339
|
+
existsSync as existsSync40,
|
|
24340
|
+
mkdirSync as mkdirSync12,
|
|
24341
|
+
appendFileSync as appendFileSync6,
|
|
24342
|
+
readFileSync as readFileSync26,
|
|
24343
|
+
readdirSync as readdirSync15,
|
|
24344
|
+
unlinkSync as unlinkSync10,
|
|
24345
|
+
writeFileSync as writeFileSync16
|
|
24346
|
+
} from "fs";
|
|
24338
24347
|
import { join as join48 } from "path";
|
|
24339
24348
|
function ensureMemoryDir(projectDir) {
|
|
24340
24349
|
const memoryPath = join48(projectDir, MEMORY_DIR);
|
|
@@ -24343,6 +24352,13 @@ function ensureMemoryDir(projectDir) {
|
|
|
24343
24352
|
}
|
|
24344
24353
|
return memoryPath;
|
|
24345
24354
|
}
|
|
24355
|
+
function ensureFullMemoryDir(projectDir) {
|
|
24356
|
+
const memoryPath = join48(projectDir, FULL_MEMORY_DIR);
|
|
24357
|
+
if (!existsSync40(memoryPath)) {
|
|
24358
|
+
mkdirSync12(memoryPath, { recursive: true });
|
|
24359
|
+
}
|
|
24360
|
+
return memoryPath;
|
|
24361
|
+
}
|
|
24346
24362
|
function getDateFileName() {
|
|
24347
24363
|
const now = new Date;
|
|
24348
24364
|
return `${now.toISOString().split("T")[0]}.md`;
|
|
@@ -24367,6 +24383,14 @@ function getDaysDiff(date) {
|
|
|
24367
24383
|
date.setHours(0, 0, 0, 0);
|
|
24368
24384
|
return Math.floor((now.getTime() - date.getTime()) / (1000 * 60 * 60 * 24));
|
|
24369
24385
|
}
|
|
24386
|
+
function extractSectionItems(block, title) {
|
|
24387
|
+
const pattern = new RegExp(`\\*\\*${title}:\\*\\*\\n([\\s\\S]*?)(?:\\n\\*\\*|$)`);
|
|
24388
|
+
const match = block.match(pattern);
|
|
24389
|
+
if (!match)
|
|
24390
|
+
return [];
|
|
24391
|
+
return match[1].split(`
|
|
24392
|
+
`).map((line) => line.trim()).filter((line) => line.startsWith("- ")).map((line) => line.replace(/^\-\s+/, "")).filter(Boolean);
|
|
24393
|
+
}
|
|
24370
24394
|
function checkArchiveNeeded(projectDir) {
|
|
24371
24395
|
const memoryDir = join48(projectDir, MEMORY_DIR);
|
|
24372
24396
|
if (!existsSync40(memoryDir)) {
|
|
@@ -24386,7 +24410,7 @@ function checkArchiveNeeded(projectDir) {
|
|
|
24386
24410
|
needsArchive: oldFiles.length > 0
|
|
24387
24411
|
};
|
|
24388
24412
|
}
|
|
24389
|
-
function archiveOldMemories(projectDir) {
|
|
24413
|
+
async function archiveOldMemories(projectDir, options) {
|
|
24390
24414
|
const checkResult = checkArchiveNeeded(projectDir);
|
|
24391
24415
|
if (!checkResult.needsArchive) {
|
|
24392
24416
|
return checkResult;
|
|
@@ -24403,13 +24427,65 @@ function archiveOldMemories(projectDir) {
|
|
|
24403
24427
|
try {
|
|
24404
24428
|
const filePath = join48(memoryDir, file);
|
|
24405
24429
|
const content = readFileSync26(filePath, "utf-8");
|
|
24430
|
+
const sessions = parseDailyLogSessions(content);
|
|
24406
24431
|
const dateMatch = file.match(/^(\d{4}-\d{2}-\d{2})/);
|
|
24407
24432
|
const dateStr = dateMatch ? dateMatch[1] : file;
|
|
24408
24433
|
archivedContent.push(`### From ${dateStr}
|
|
24409
24434
|
|
|
24410
24435
|
`);
|
|
24411
|
-
const
|
|
24412
|
-
|
|
24436
|
+
const summaryBlocks = [];
|
|
24437
|
+
const deepBlocks = [];
|
|
24438
|
+
for (const session of sessions) {
|
|
24439
|
+
summaryBlocks.push(session.raw, "", "---", "");
|
|
24440
|
+
if (session.sessionID && shouldDeepSummarize(session)) {
|
|
24441
|
+
const fullPath = getFullTranscriptPath(projectDir, session.sessionID);
|
|
24442
|
+
if (existsSync40(fullPath)) {
|
|
24443
|
+
const fullContent = readFileSync26(fullPath, "utf-8");
|
|
24444
|
+
let deepSummary = null;
|
|
24445
|
+
if (options?.deepSummarizer) {
|
|
24446
|
+
deepSummary = await options.deepSummarizer(session, fullContent);
|
|
24447
|
+
}
|
|
24448
|
+
if (!deepSummary) {
|
|
24449
|
+
const summary = summarizeFullTranscript(fullContent);
|
|
24450
|
+
if (summary.userPreferences.length > 0 || summary.decisions.length > 0 || summary.lessons.length > 0) {
|
|
24451
|
+
deepSummary = [
|
|
24452
|
+
summary.userPreferences.length > 0 ? [
|
|
24453
|
+
"**User Preferences:**",
|
|
24454
|
+
...summary.userPreferences.map((item) => `- ${item}`),
|
|
24455
|
+
""
|
|
24456
|
+
].join(`
|
|
24457
|
+
`) : "",
|
|
24458
|
+
summary.decisions.length > 0 ? [
|
|
24459
|
+
"**Decisions Made:**",
|
|
24460
|
+
...summary.decisions.map((item) => `- ${item}`),
|
|
24461
|
+
""
|
|
24462
|
+
].join(`
|
|
24463
|
+
`) : "",
|
|
24464
|
+
summary.lessons.length > 0 ? [
|
|
24465
|
+
"**Lessons Learned:**",
|
|
24466
|
+
...summary.lessons.map((item) => `- ${item}`),
|
|
24467
|
+
""
|
|
24468
|
+
].join(`
|
|
24469
|
+
`) : ""
|
|
24470
|
+
].filter(Boolean).join(`
|
|
24471
|
+
`);
|
|
24472
|
+
}
|
|
24473
|
+
}
|
|
24474
|
+
if (deepSummary) {
|
|
24475
|
+
deepBlocks.push(`#### Deep Summary (${session.sessionID.slice(0, 12)})`, "", deepSummary.trim(), "", "---", "");
|
|
24476
|
+
}
|
|
24477
|
+
}
|
|
24478
|
+
}
|
|
24479
|
+
}
|
|
24480
|
+
archivedContent.push(summaryBlocks.join(`
|
|
24481
|
+
`));
|
|
24482
|
+
if (deepBlocks.length > 0) {
|
|
24483
|
+
archivedContent.push(`#### Deep Summaries
|
|
24484
|
+
|
|
24485
|
+
`);
|
|
24486
|
+
archivedContent.push(deepBlocks.join(`
|
|
24487
|
+
`));
|
|
24488
|
+
}
|
|
24413
24489
|
unlinkSync10(filePath);
|
|
24414
24490
|
} catch {
|
|
24415
24491
|
continue;
|
|
@@ -24430,12 +24506,108 @@ This file contains archived conversation memories.
|
|
|
24430
24506
|
needsArchive: false
|
|
24431
24507
|
};
|
|
24432
24508
|
}
|
|
24509
|
+
function parseDailyLogSessions(content) {
|
|
24510
|
+
const cleaned = content.replace(/^# Memory Log.*\n\n?/, "");
|
|
24511
|
+
const blocks = cleaned.split(/\n---\n/).map((block) => block.trim()).filter(Boolean);
|
|
24512
|
+
return blocks.map((block) => {
|
|
24513
|
+
const sessionMatch = block.match(/SessionID:\s*(.+)/);
|
|
24514
|
+
const tags = extractSectionItems(block, "Tags");
|
|
24515
|
+
const decisions = extractSectionItems(block, "Decisions");
|
|
24516
|
+
const todos = extractSectionItems(block, "TODOs").map((item) => item.replace(/^\[ \]\s+/, ""));
|
|
24517
|
+
return {
|
|
24518
|
+
sessionID: sessionMatch?.[1]?.trim(),
|
|
24519
|
+
raw: block,
|
|
24520
|
+
tags,
|
|
24521
|
+
decisions,
|
|
24522
|
+
todos
|
|
24523
|
+
};
|
|
24524
|
+
});
|
|
24525
|
+
}
|
|
24526
|
+
function shouldDeepSummarize(session) {
|
|
24527
|
+
if (session.decisions.length > 0 || session.todos.length > 0)
|
|
24528
|
+
return true;
|
|
24529
|
+
return session.tags.some((tag) => DEEP_SUMMARY_TAGS.includes(tag.toLowerCase()));
|
|
24530
|
+
}
|
|
24531
|
+
function getFullTranscriptPath(projectDir, sessionID) {
|
|
24532
|
+
const safeSessionID = sessionID.replace(/[^a-zA-Z0-9_-]/g, "_");
|
|
24533
|
+
return join48(projectDir, FULL_MEMORY_DIR, `${safeSessionID}.md`);
|
|
24534
|
+
}
|
|
24535
|
+
function parseFullTranscript(content) {
|
|
24536
|
+
const cleaned = content.replace(/^# Full Transcript.*\n.*\n\n?/, "");
|
|
24537
|
+
const blocks = cleaned.split(/\n---\n/).map((block) => block.trim()).filter(Boolean);
|
|
24538
|
+
return blocks.map((block) => {
|
|
24539
|
+
const lines = block.split(`
|
|
24540
|
+
`);
|
|
24541
|
+
const title = lines.shift() || "";
|
|
24542
|
+
const roleMatch = title.match(/^##\s+([A-Z]+)\b/);
|
|
24543
|
+
return {
|
|
24544
|
+
role: roleMatch?.[1]?.toLowerCase() || "unknown",
|
|
24545
|
+
content: lines.join(`
|
|
24546
|
+
`).trim()
|
|
24547
|
+
};
|
|
24548
|
+
});
|
|
24549
|
+
}
|
|
24550
|
+
function summarizeFullTranscript(content) {
|
|
24551
|
+
const blocks = parseFullTranscript(content);
|
|
24552
|
+
const userPreferences = [];
|
|
24553
|
+
const decisions = [];
|
|
24554
|
+
const lessons = [];
|
|
24555
|
+
for (const block of blocks) {
|
|
24556
|
+
const text = block.content;
|
|
24557
|
+
const preferencePatterns = [
|
|
24558
|
+
/\u504F\u597D[\uFF1A:](.+)/g,
|
|
24559
|
+
/\u559C\u6B22[\uFF1A:](.+)/g,
|
|
24560
|
+
/prefer(?:s|red)?[\uFF1A:](.+)/gi,
|
|
24561
|
+
/preference(?:s)?[\uFF1A:](.+)/gi
|
|
24562
|
+
];
|
|
24563
|
+
for (const pattern of preferencePatterns) {
|
|
24564
|
+
for (const match of text.matchAll(pattern)) {
|
|
24565
|
+
if (match[1])
|
|
24566
|
+
userPreferences.push(match[1].trim());
|
|
24567
|
+
}
|
|
24568
|
+
}
|
|
24569
|
+
const decisionPatterns = [
|
|
24570
|
+
/\u51B3\u5B9A[\uFF1A:](.+)/g,
|
|
24571
|
+
/\u51B3\u7B56[\uFF1A:](.+)/g,
|
|
24572
|
+
/decided?[\uFF1A:](.+)/gi,
|
|
24573
|
+
/chosen?[\uFF1A:](.+)/gi,
|
|
24574
|
+
/going with\s+(.+)/gi,
|
|
24575
|
+
/will use\s+(.+)/gi
|
|
24576
|
+
];
|
|
24577
|
+
for (const pattern of decisionPatterns) {
|
|
24578
|
+
for (const match of text.matchAll(pattern)) {
|
|
24579
|
+
if (match[1])
|
|
24580
|
+
decisions.push(match[1].trim());
|
|
24581
|
+
}
|
|
24582
|
+
}
|
|
24583
|
+
const lessonPatterns = [
|
|
24584
|
+
/\u7ED3\u8BBA[\uFF1A:](.+)/g,
|
|
24585
|
+
/\u7ECF\u9A8C[\uFF1A:](.+)/g,
|
|
24586
|
+
/\u6559\u8BAD[\uFF1A:](.+)/g,
|
|
24587
|
+
/lesson(?:s)?[\uFF1A:](.+)/gi,
|
|
24588
|
+
/insight(?:s)?[\uFF1A:](.+)/gi
|
|
24589
|
+
];
|
|
24590
|
+
for (const pattern of lessonPatterns) {
|
|
24591
|
+
for (const match of text.matchAll(pattern)) {
|
|
24592
|
+
if (match[1])
|
|
24593
|
+
lessons.push(match[1].trim());
|
|
24594
|
+
}
|
|
24595
|
+
}
|
|
24596
|
+
}
|
|
24597
|
+
const unique = (items) => Array.from(new Set(items)).slice(0, 10);
|
|
24598
|
+
return {
|
|
24599
|
+
userPreferences: unique(userPreferences),
|
|
24600
|
+
decisions: unique(decisions),
|
|
24601
|
+
lessons: unique(lessons)
|
|
24602
|
+
};
|
|
24603
|
+
}
|
|
24433
24604
|
function appendMemoryEntry(projectDir, entry) {
|
|
24434
24605
|
try {
|
|
24435
24606
|
const memoryDir = ensureMemoryDir(projectDir);
|
|
24436
24607
|
const filePath = join48(memoryDir, getDateFileName());
|
|
24437
24608
|
const sections = [
|
|
24438
24609
|
`## Session: ${entry.sessionID.slice(0, 12)} (${formatTime(new Date(entry.timestamp))})`,
|
|
24610
|
+
`SessionID: ${entry.sessionID}`,
|
|
24439
24611
|
""
|
|
24440
24612
|
];
|
|
24441
24613
|
if (entry.summary) {
|
|
@@ -24457,6 +24629,11 @@ function appendMemoryEntry(projectDir, entry) {
|
|
|
24457
24629
|
entry.todos.forEach((todo) => sections.push(`- [ ] ${todo}`));
|
|
24458
24630
|
sections.push("");
|
|
24459
24631
|
}
|
|
24632
|
+
if (entry.tags && entry.tags.length > 0) {
|
|
24633
|
+
sections.push("**Tags:**");
|
|
24634
|
+
entry.tags.forEach((tag) => sections.push(`- ${tag}`));
|
|
24635
|
+
sections.push("");
|
|
24636
|
+
}
|
|
24460
24637
|
sections.push("---", "");
|
|
24461
24638
|
const content = sections.join(`
|
|
24462
24639
|
`);
|
|
@@ -24480,7 +24657,28 @@ function hasMemoryForSession(projectDir, sessionID) {
|
|
|
24480
24657
|
if (!existsSync40(filePath))
|
|
24481
24658
|
return false;
|
|
24482
24659
|
const content = readFileSync26(filePath, "utf-8");
|
|
24483
|
-
return content.includes(`Session: ${sessionID.slice(0, 12)}`);
|
|
24660
|
+
return content.includes(`SessionID: ${sessionID}`) || content.includes(`Session: ${sessionID.slice(0, 12)}`);
|
|
24661
|
+
} catch {
|
|
24662
|
+
return false;
|
|
24663
|
+
}
|
|
24664
|
+
}
|
|
24665
|
+
function saveFullTranscript(projectDir, sessionID, messages) {
|
|
24666
|
+
try {
|
|
24667
|
+
const memoryDir = ensureFullMemoryDir(projectDir);
|
|
24668
|
+
const safeSessionID = sessionID.replace(/[^a-zA-Z0-9_-]/g, "_");
|
|
24669
|
+
const filePath = join48(memoryDir, `${safeSessionID}.md`);
|
|
24670
|
+
const sections = [
|
|
24671
|
+
`# Full Transcript - ${sessionID}`,
|
|
24672
|
+
`Generated: ${new Date().toISOString()}`,
|
|
24673
|
+
""
|
|
24674
|
+
];
|
|
24675
|
+
for (const message of messages) {
|
|
24676
|
+
const timestamp2 = message.timestamp ? ` (${message.timestamp})` : "";
|
|
24677
|
+
sections.push(`## ${message.role.toUpperCase()}${timestamp2}`, "", message.text || "", "", "---", "");
|
|
24678
|
+
}
|
|
24679
|
+
writeFileSync16(filePath, sections.join(`
|
|
24680
|
+
`));
|
|
24681
|
+
return true;
|
|
24484
24682
|
} catch {
|
|
24485
24683
|
return false;
|
|
24486
24684
|
}
|
|
@@ -24499,10 +24697,17 @@ function truncateText(text, maxLength) {
|
|
|
24499
24697
|
function extractSessionSummary(sessionID, messages) {
|
|
24500
24698
|
const userMessages = [];
|
|
24501
24699
|
const assistantMessages = [];
|
|
24700
|
+
const tags = [];
|
|
24502
24701
|
for (const msg of messages) {
|
|
24503
24702
|
const text = extractTextFromParts(msg.parts);
|
|
24504
24703
|
if (!text.trim())
|
|
24505
24704
|
continue;
|
|
24705
|
+
const tagMatches = text.matchAll(/#([a-zA-Z][\w-]{1,30})/g);
|
|
24706
|
+
for (const match of tagMatches) {
|
|
24707
|
+
const tag = match[1]?.toLowerCase();
|
|
24708
|
+
if (tag)
|
|
24709
|
+
tags.push(`#${tag}`);
|
|
24710
|
+
}
|
|
24506
24711
|
if (msg.info.role === "user") {
|
|
24507
24712
|
userMessages.push(truncateText(text, 500));
|
|
24508
24713
|
} else if (msg.info.role === "assistant") {
|
|
@@ -24559,15 +24764,60 @@ function extractSessionSummary(sessionID, messages) {
|
|
|
24559
24764
|
summary,
|
|
24560
24765
|
keyPoints: [...new Set(keyPoints)].slice(0, 5),
|
|
24561
24766
|
decisions: [...new Set(decisions)].slice(0, 3),
|
|
24562
|
-
todos: [...new Set(todos)].slice(0, 3)
|
|
24767
|
+
todos: [...new Set(todos)].slice(0, 3),
|
|
24768
|
+
tags: [...new Set(tags)].slice(0, 5)
|
|
24563
24769
|
};
|
|
24564
24770
|
}
|
|
24565
24771
|
|
|
24566
24772
|
// src/hooks/memory-system/index.ts
|
|
24773
|
+
function extractMessageText(parts) {
|
|
24774
|
+
return parts.filter((p) => p.type === "text" && p.text).map((p) => p.text).join(`
|
|
24775
|
+
`);
|
|
24776
|
+
}
|
|
24777
|
+
function extractFullTranscript(messages) {
|
|
24778
|
+
return messages.map((message) => {
|
|
24779
|
+
const text = extractMessageText(message.parts);
|
|
24780
|
+
return {
|
|
24781
|
+
role: message.info.role,
|
|
24782
|
+
text
|
|
24783
|
+
};
|
|
24784
|
+
}).filter((message) => message.text.trim().length > 0);
|
|
24785
|
+
}
|
|
24567
24786
|
var ARCHIVE_CHECK_COOLDOWN_MS = 60 * 60 * 1000;
|
|
24568
24787
|
function createMemorySystemHook(ctx) {
|
|
24569
24788
|
const sessionStates = new Map;
|
|
24570
24789
|
const archiveState = { inProgress: false, lastCheck: 0 };
|
|
24790
|
+
async function runArchivistSummary(prompt) {
|
|
24791
|
+
try {
|
|
24792
|
+
const createResult = await ctx.client.session.create({
|
|
24793
|
+
body: {
|
|
24794
|
+
title: "Memory: Deep Summary"
|
|
24795
|
+
}
|
|
24796
|
+
});
|
|
24797
|
+
if (createResult.error)
|
|
24798
|
+
return null;
|
|
24799
|
+
const sessionID = createResult.data.id;
|
|
24800
|
+
subagentSessions.add(sessionID);
|
|
24801
|
+
await ctx.client.session.prompt({
|
|
24802
|
+
path: { id: sessionID },
|
|
24803
|
+
body: {
|
|
24804
|
+
agent: "archivist",
|
|
24805
|
+
parts: [{ type: "text", text: prompt }]
|
|
24806
|
+
}
|
|
24807
|
+
});
|
|
24808
|
+
const messagesResult = await ctx.client.session.messages({
|
|
24809
|
+
path: { id: sessionID }
|
|
24810
|
+
});
|
|
24811
|
+
const messages = messagesResult.data ?? messagesResult;
|
|
24812
|
+
const assistantMessages = messages.filter((m) => m.info.role === "assistant");
|
|
24813
|
+
const last = assistantMessages[assistantMessages.length - 1];
|
|
24814
|
+
const text = last?.parts.filter((p) => p.type === "text" && p.text).map((p) => p.text).join(`
|
|
24815
|
+
`).trim();
|
|
24816
|
+
return text || null;
|
|
24817
|
+
} catch {
|
|
24818
|
+
return null;
|
|
24819
|
+
}
|
|
24820
|
+
}
|
|
24571
24821
|
function getOrCreateState(sessionID) {
|
|
24572
24822
|
let state2 = sessionStates.get(sessionID);
|
|
24573
24823
|
if (!state2) {
|
|
@@ -24611,7 +24861,34 @@ function createMemorySystemHook(ctx) {
|
|
|
24611
24861
|
count: checkResult.archived.length
|
|
24612
24862
|
});
|
|
24613
24863
|
try {
|
|
24614
|
-
const result = archiveOldMemories(ctx.directory
|
|
24864
|
+
const result = await archiveOldMemories(ctx.directory, {
|
|
24865
|
+
deepSummarizer: async (session, fullContent) => {
|
|
24866
|
+
const prompt = `Summarize the full transcript into long-term memory entries.
|
|
24867
|
+
|
|
24868
|
+
Rules:
|
|
24869
|
+
- Output Markdown only.
|
|
24870
|
+
- Use sections only when relevant.
|
|
24871
|
+
- Keep each bullet concise (<120 chars).
|
|
24872
|
+
- Do NOT include sensitive data or raw conversation.
|
|
24873
|
+
- If nothing important, return "".
|
|
24874
|
+
|
|
24875
|
+
Required sections (only if non-empty):
|
|
24876
|
+
**User Preferences:**
|
|
24877
|
+
- ...
|
|
24878
|
+
**Decisions Made:**
|
|
24879
|
+
- ...
|
|
24880
|
+
**Lessons Learned:**
|
|
24881
|
+
- ...
|
|
24882
|
+
|
|
24883
|
+
Transcript:
|
|
24884
|
+
${fullContent}`;
|
|
24885
|
+
try {
|
|
24886
|
+
return await runArchivistSummary(prompt);
|
|
24887
|
+
} catch {
|
|
24888
|
+
return null;
|
|
24889
|
+
}
|
|
24890
|
+
}
|
|
24891
|
+
});
|
|
24615
24892
|
await ctx.client.tui.showToast({
|
|
24616
24893
|
body: {
|
|
24617
24894
|
title: "Memory Archived",
|
|
@@ -24647,17 +24924,11 @@ function createMemorySystemHook(ctx) {
|
|
|
24647
24924
|
query: { directory: ctx.directory }
|
|
24648
24925
|
});
|
|
24649
24926
|
const messages = resp.data ?? resp;
|
|
24650
|
-
if (messages.length < MIN_MESSAGES_TO_SAVE) {
|
|
24651
|
-
log(`[${HOOK_NAME6}] Too few messages`, {
|
|
24652
|
-
sessionID,
|
|
24653
|
-
count: messages.length,
|
|
24654
|
-
threshold: MIN_MESSAGES_TO_SAVE
|
|
24655
|
-
});
|
|
24656
|
-
return;
|
|
24657
|
-
}
|
|
24658
24927
|
const entry = extractSessionSummary(sessionID, messages);
|
|
24928
|
+
const fullTranscript = extractFullTranscript(messages);
|
|
24659
24929
|
const success = appendMemoryEntry(ctx.directory, entry);
|
|
24660
|
-
|
|
24930
|
+
const fullSuccess = saveFullTranscript(ctx.directory, sessionID, fullTranscript);
|
|
24931
|
+
if (success && fullSuccess) {
|
|
24661
24932
|
state2.saved = true;
|
|
24662
24933
|
log(`[${HOOK_NAME6}] Memory saved`, {
|
|
24663
24934
|
sessionID,
|
|
@@ -24666,7 +24937,11 @@ function createMemorySystemHook(ctx) {
|
|
|
24666
24937
|
});
|
|
24667
24938
|
await checkAndArchive();
|
|
24668
24939
|
} else {
|
|
24669
|
-
log(`[${HOOK_NAME6}] Failed to save memory`, {
|
|
24940
|
+
log(`[${HOOK_NAME6}] Failed to save memory`, {
|
|
24941
|
+
sessionID,
|
|
24942
|
+
summarySaved: success,
|
|
24943
|
+
fullSaved: fullSuccess
|
|
24944
|
+
});
|
|
24670
24945
|
}
|
|
24671
24946
|
} catch (err) {
|
|
24672
24947
|
log(`[${HOOK_NAME6}] Error saving memory`, { sessionID, error: String(err) });
|
|
@@ -30031,7 +30306,7 @@ ${msg}`);
|
|
|
30031
30306
|
// src/tools/lsp/utils.ts
|
|
30032
30307
|
import { extname as extname2, resolve as resolve7 } from "path";
|
|
30033
30308
|
import { fileURLToPath as fileURLToPath3 } from "url";
|
|
30034
|
-
import { existsSync as existsSync43, readFileSync as readFileSync30, writeFileSync as
|
|
30309
|
+
import { existsSync as existsSync43, readFileSync as readFileSync30, writeFileSync as writeFileSync17 } from "fs";
|
|
30035
30310
|
function findWorkspaceRoot(filePath) {
|
|
30036
30311
|
let dir = resolve7(filePath);
|
|
30037
30312
|
if (!existsSync43(dir) || !__require("fs").statSync(dir).isDirectory()) {
|
|
@@ -30261,7 +30536,7 @@ function applyTextEditsToFile(filePath, edits) {
|
|
|
30261
30536
|
`));
|
|
30262
30537
|
}
|
|
30263
30538
|
}
|
|
30264
|
-
|
|
30539
|
+
writeFileSync17(filePath, lines.join(`
|
|
30265
30540
|
`), "utf-8");
|
|
30266
30541
|
return { success: true, editCount: edits.length };
|
|
30267
30542
|
} catch (err) {
|
|
@@ -30292,7 +30567,7 @@ function applyWorkspaceEdit(edit) {
|
|
|
30292
30567
|
if (change.kind === "create") {
|
|
30293
30568
|
try {
|
|
30294
30569
|
const filePath = uriToPath(change.uri);
|
|
30295
|
-
|
|
30570
|
+
writeFileSync17(filePath, "", "utf-8");
|
|
30296
30571
|
result.filesModified.push(filePath);
|
|
30297
30572
|
} catch (err) {
|
|
30298
30573
|
result.success = false;
|
|
@@ -30303,7 +30578,7 @@ function applyWorkspaceEdit(edit) {
|
|
|
30303
30578
|
const oldPath = uriToPath(change.oldUri);
|
|
30304
30579
|
const newPath = uriToPath(change.newUri);
|
|
30305
30580
|
const content = readFileSync30(oldPath, "utf-8");
|
|
30306
|
-
|
|
30581
|
+
writeFileSync17(newPath, content, "utf-8");
|
|
30307
30582
|
__require("fs").unlinkSync(oldPath);
|
|
30308
30583
|
result.filesModified.push(newPath);
|
|
30309
30584
|
} catch (err) {
|
|
@@ -52221,6 +52496,8 @@ var MEMORY_CONSOLIDATE_TEMPLATE = `Consolidate daily memory logs into long-term
|
|
|
52221
52496
|
## WHAT TO DO
|
|
52222
52497
|
|
|
52223
52498
|
Read all files in \`.opencode/memory/\` directory and consolidate important information into \`.opencode/MEMORY.md\`.
|
|
52499
|
+
If a daily log entry contains Decisions, TODOs, or critical tags (#project, #preference, #policy, #important),
|
|
52500
|
+
also pull the full transcript from \`.opencode/memory/full/<sessionID>.md\` for deeper summarization.
|
|
52224
52501
|
|
|
52225
52502
|
### Step 1: Read Daily Logs
|
|
52226
52503
|
|
|
@@ -52240,6 +52517,9 @@ From the daily logs, identify:
|
|
|
52240
52517
|
4. **Recurring Problems** - Issues that came up multiple times and their solutions
|
|
52241
52518
|
5. **Key Insights** - Valuable learnings worth preserving
|
|
52242
52519
|
|
|
52520
|
+
If deep-summary trigger conditions are met, read the full transcript for that session and extract additional
|
|
52521
|
+
preferences, decisions, and lessons.
|
|
52522
|
+
|
|
52243
52523
|
### Step 3: Update MEMORY.md
|
|
52244
52524
|
|
|
52245
52525
|
Read existing \`.opencode/MEMORY.md\` (create if doesn't exist).
|
|
@@ -52259,6 +52539,9 @@ Append new consolidated information using this structure:
|
|
|
52259
52539
|
### Lessons Learned
|
|
52260
52540
|
- [insight or lesson]
|
|
52261
52541
|
|
|
52542
|
+
### Deep Summaries (when triggered)
|
|
52543
|
+
- [sessionID] preference/decision/lesson from full transcript
|
|
52544
|
+
|
|
52262
52545
|
---
|
|
52263
52546
|
\`\`\`
|
|
52264
52547
|
|