@kevin0181/memoc 1.2.0 → 1.3.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 +3 -3
- package/bin/cli.js +90 -73
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -113,7 +113,7 @@ npx @kevin0181/memoc tokens
|
|
|
113
113
|
# Archive and compact an oversized startup summary
|
|
114
114
|
npx @kevin0181/memoc trim-summary
|
|
115
115
|
|
|
116
|
-
#
|
|
116
|
+
# Compact oversized memoc files and refresh generated indexes
|
|
117
117
|
npx @kevin0181/memoc compress
|
|
118
118
|
|
|
119
119
|
# Add the same protocol to another agent's entry file
|
|
@@ -228,7 +228,7 @@ Startup cost is kept minimal by design.
|
|
|
228
228
|
|
|
229
229
|
Everything else is on-demand. Use `memoc tokens` to see the live breakdown for your project.
|
|
230
230
|
|
|
231
|
-
`session-summary.md` is a replace-only startup snapshot, not a timeline. If it grows beyond the warning threshold, run `memoc trim-summary`; completed history belongs in `.memoc/worklog/<actor>/YYYY-MM/`, and unfinished/risky resume detail belongs in `.memoc/04-handoff.md`.
|
|
231
|
+
`session-summary.md` is a replace-only startup snapshot, not a timeline. If it grows beyond the warning threshold, run `memoc compress` or `memoc trim-summary`; completed history belongs in `.memoc/worklog/<actor>/YYYY-MM/`, and unfinished/risky resume detail belongs in `.memoc/04-handoff.md`.
|
|
232
232
|
|
|
233
233
|
---
|
|
234
234
|
|
|
@@ -294,7 +294,7 @@ Node.js · Next.js · React · Vue · Svelte · Angular · Nuxt · Astro · Expr
|
|
|
294
294
|
- **New project** — scaffolds all memory files with sensible defaults.
|
|
295
295
|
- **Existing project** — detects your stack and fills in real project info (name, scripts, config files).
|
|
296
296
|
- **Already initialized** — `init` injects the managed block without touching your existing content. `update` re-scans and refreshes project-specific sections.
|
|
297
|
-
- **Long-running projects** — use actor worklogs for history; `compress`
|
|
297
|
+
- **Long-running projects** — use actor worklogs for history; run `compress` to trim startup memory, archive legacy logs, and refresh generated activity indexes.
|
|
298
298
|
|
|
299
299
|
---
|
|
300
300
|
|
package/bin/cli.js
CHANGED
|
@@ -981,7 +981,9 @@ function collectMemocMarkdownFiles(dir) {
|
|
|
981
981
|
}
|
|
982
982
|
walk(path.join(dir, '.memoc'));
|
|
983
983
|
walk(path.join(dir, 'skills', 'project-memory-maintainer'));
|
|
984
|
-
return files
|
|
984
|
+
return files
|
|
985
|
+
.filter(fp => !/session-summary-archive(?:-\d+)?\.md$/.test(path.basename(fp)))
|
|
986
|
+
.sort();
|
|
985
987
|
}
|
|
986
988
|
|
|
987
989
|
function ensureMemocFrontmatter(filePath, dir) {
|
|
@@ -3292,51 +3294,29 @@ function searchPriority(file, scope = 'memory') {
|
|
|
3292
3294
|
// ═══════════════════════════════════════════════════════════════════
|
|
3293
3295
|
|
|
3294
3296
|
function runTokens(dir) {
|
|
3295
|
-
const
|
|
3296
|
-
const read = fp => { try { return fs.readFileSync(fp, 'utf8'); } catch { return ''; } };
|
|
3297
|
-
const memDir = path.join(dir, '.memoc');
|
|
3298
|
-
|
|
3299
|
-
const startup = [
|
|
3300
|
-
['CLAUDE.md', path.join(dir, 'CLAUDE.md')],
|
|
3301
|
-
['session-summary.md', path.join(memDir, 'session-summary.md')],
|
|
3302
|
-
];
|
|
3303
|
-
const onDemand = [
|
|
3304
|
-
['llms.txt', path.join(dir, 'llms.txt')],
|
|
3305
|
-
['02-current-project-state.md', path.join(memDir, '02-current-project-state.md')],
|
|
3306
|
-
['03-decisions.md', path.join(memDir, '03-decisions.md')],
|
|
3307
|
-
['04-handoff.md', path.join(memDir, '04-handoff.md')],
|
|
3308
|
-
['06-project-rules.md', path.join(memDir, '06-project-rules.md')],
|
|
3309
|
-
['activity.md', path.join(memDir, 'activity.md')],
|
|
3310
|
-
];
|
|
3297
|
+
const stats = memoryTokenStats(dir);
|
|
3311
3298
|
|
|
3312
3299
|
console.log('\n memoc tokens\n');
|
|
3313
3300
|
let startupTotal = 0;
|
|
3314
3301
|
console.log(' Startup (always loaded):');
|
|
3315
|
-
for (const
|
|
3316
|
-
|
|
3317
|
-
const
|
|
3318
|
-
|
|
3319
|
-
startupTotal += t;
|
|
3320
|
-
const warn = b > 1000 ? ' ⚠ large' : '';
|
|
3321
|
-
console.log(` ${name.padEnd(32)} ${String(t).padStart(5)} tokens (${b}B)${warn}`);
|
|
3302
|
+
for (const item of stats.startup) {
|
|
3303
|
+
startupTotal += item.tokens;
|
|
3304
|
+
const warn = item.bytes > 1000 ? ' ⚠ large' : '';
|
|
3305
|
+
console.log(` ${item.name.padEnd(32)} ${String(item.tokens).padStart(5)} tokens (${item.bytes}B)${warn}`);
|
|
3322
3306
|
}
|
|
3323
3307
|
console.log(` ${'── startup total'.padEnd(32)} ${String(startupTotal).padStart(5)} tokens`);
|
|
3324
3308
|
|
|
3325
3309
|
console.log('\n On-demand (read when needed):');
|
|
3326
3310
|
let onDemandTotal = 0;
|
|
3327
|
-
for (const
|
|
3328
|
-
|
|
3329
|
-
|
|
3330
|
-
|
|
3331
|
-
const b = Buffer.byteLength(content, 'utf8');
|
|
3332
|
-
onDemandTotal += t;
|
|
3333
|
-
const warn = t > 500 ? ' ⚠ consider compress' : '';
|
|
3334
|
-
console.log(` ${name.padEnd(32)} ${String(t).padStart(5)} tokens (${b}B)${warn}`);
|
|
3311
|
+
for (const item of stats.onDemand) {
|
|
3312
|
+
onDemandTotal += item.tokens;
|
|
3313
|
+
const warn = item.tokens > 500 ? ' ⚠ consider compress' : '';
|
|
3314
|
+
console.log(` ${item.name.padEnd(32)} ${String(item.tokens).padStart(5)} tokens (${item.bytes}B)${warn}`);
|
|
3335
3315
|
}
|
|
3336
3316
|
console.log(` ${'── on-demand total'.padEnd(32)} ${String(onDemandTotal).padStart(5)} tokens`);
|
|
3337
3317
|
console.log(`\n If all loaded: ~${startupTotal + onDemandTotal} tokens`);
|
|
3338
3318
|
|
|
3339
|
-
const summaryContent =
|
|
3319
|
+
const summaryContent = safeRead(path.join(dir, '.memoc', 'session-summary.md'));
|
|
3340
3320
|
const summaryBytes = Buffer.byteLength(summaryContent, 'utf8');
|
|
3341
3321
|
if (summaryBytes > 800) {
|
|
3342
3322
|
console.log(`\n ⚠ session-summary.md is ${summaryBytes}B — recommended <800B. Run \`memoc trim-summary\`, then move completed history to worklog and resume details to 04-handoff.md.`);
|
|
@@ -3344,6 +3324,37 @@ function runTokens(dir) {
|
|
|
3344
3324
|
console.log();
|
|
3345
3325
|
}
|
|
3346
3326
|
|
|
3327
|
+
function memoryTokenStats(dir) {
|
|
3328
|
+
const est = text => Math.ceil(Buffer.byteLength(text, 'utf8') / 4);
|
|
3329
|
+
const read = fp => { try { return fs.readFileSync(fp, 'utf8'); } catch { return ''; } };
|
|
3330
|
+
const memDir = path.join(dir, '.memoc');
|
|
3331
|
+
const startup = [
|
|
3332
|
+
['CLAUDE.md', path.join(dir, 'CLAUDE.md'), true],
|
|
3333
|
+
['session-summary.md', path.join(memDir, 'session-summary.md'), true],
|
|
3334
|
+
];
|
|
3335
|
+
const onDemand = [
|
|
3336
|
+
['llms.txt', path.join(dir, 'llms.txt')],
|
|
3337
|
+
['02-current-project-state.md', path.join(memDir, '02-current-project-state.md')],
|
|
3338
|
+
['03-decisions.md', path.join(memDir, '03-decisions.md')],
|
|
3339
|
+
['04-handoff.md', path.join(memDir, '04-handoff.md')],
|
|
3340
|
+
['06-project-rules.md', path.join(memDir, '06-project-rules.md')],
|
|
3341
|
+
['activity.md', path.join(memDir, 'activity.md')],
|
|
3342
|
+
];
|
|
3343
|
+
const existing = ([, fp, keep]) => keep || fs.existsSync(fp);
|
|
3344
|
+
const toItem = ([name, fp]) => {
|
|
3345
|
+
const content = read(fp);
|
|
3346
|
+
return { name, fp, bytes: Buffer.byteLength(content, 'utf8'), tokens: est(content) };
|
|
3347
|
+
};
|
|
3348
|
+
const startupItems = startup.filter(existing).map(toItem);
|
|
3349
|
+
const onDemandItems = onDemand.filter(existing).map(toItem);
|
|
3350
|
+
return {
|
|
3351
|
+
startup: startupItems,
|
|
3352
|
+
onDemand: onDemandItems,
|
|
3353
|
+
startupTokens: startupItems.reduce((sum, item) => sum + item.tokens, 0),
|
|
3354
|
+
allTokens: [...startupItems, ...onDemandItems].reduce((sum, item) => sum + item.tokens, 0),
|
|
3355
|
+
};
|
|
3356
|
+
}
|
|
3357
|
+
|
|
3347
3358
|
// ═══════════════════════════════════════════════════════════════════
|
|
3348
3359
|
// DOCTOR — quick health checks for shared memoc repos
|
|
3349
3360
|
// ═══════════════════════════════════════════════════════════════════
|
|
@@ -3394,47 +3405,46 @@ function runDoctor(dir) {
|
|
|
3394
3405
|
}
|
|
3395
3406
|
|
|
3396
3407
|
// ═══════════════════════════════════════════════════════════════════
|
|
3397
|
-
// COMPRESS —
|
|
3408
|
+
// COMPRESS — safe whole-memory compaction
|
|
3398
3409
|
// ═══════════════════════════════════════════════════════════════════
|
|
3399
3410
|
|
|
3400
3411
|
function runCompress(dir) {
|
|
3401
|
-
|
|
3402
|
-
const
|
|
3403
|
-
const
|
|
3404
|
-
|
|
3405
|
-
if (!fs.existsSync(logPath)) {
|
|
3406
|
-
console.log('\n No .memoc/log.md found.\n');
|
|
3407
|
-
return;
|
|
3408
|
-
}
|
|
3409
|
-
|
|
3410
|
-
const src = fs.readFileSync(logPath, 'utf8');
|
|
3411
|
-
// Split on entry headers, keep header as part of each chunk
|
|
3412
|
-
const parts = src.split(/(?=\n## \[)/);
|
|
3413
|
-
const header = parts[0]; // everything before first entry
|
|
3414
|
-
const entries = parts.slice(1).filter(e => e.trim());
|
|
3412
|
+
ensureMemocBase(dir);
|
|
3413
|
+
const before = memoryTokenStats(dir);
|
|
3414
|
+
const actions = [];
|
|
3415
|
+
const mark = (label, name) => actions.push(` ${label.padEnd(8)} ${name}`);
|
|
3415
3416
|
|
|
3416
|
-
|
|
3417
|
-
|
|
3418
|
-
|
|
3417
|
+
console.log(`\n memoc compress\n`);
|
|
3418
|
+
archiveLegacyLog(dir, mark);
|
|
3419
|
+
|
|
3420
|
+
const trim = trimSummaryFile(dir);
|
|
3421
|
+
if (trim.action === 'trim') {
|
|
3422
|
+
mark('update', `.memoc/session-summary.md (${trim.beforeBytes}B -> ${trim.afterBytes}B)`);
|
|
3423
|
+
mark('update', '.memoc/session-summary-archive.md');
|
|
3424
|
+
} else if (trim.action === 'add') {
|
|
3425
|
+
mark('add', '.memoc/session-summary.md');
|
|
3426
|
+
} else {
|
|
3427
|
+
mark('skip', `.memoc/session-summary.md (compact ${trim.beforeBytes || 0}B)`);
|
|
3419
3428
|
}
|
|
3420
3429
|
|
|
3421
|
-
const
|
|
3422
|
-
const
|
|
3423
|
-
|
|
3424
|
-
|
|
3425
|
-
|
|
3426
|
-
|
|
3427
|
-
|
|
3428
|
-
|
|
3429
|
-
|
|
3430
|
-
|
|
3431
|
-
|
|
3432
|
-
|
|
3433
|
-
|
|
3434
|
-
|
|
3435
|
-
console.log(
|
|
3436
|
-
|
|
3437
|
-
console.log(`
|
|
3430
|
+
const workRoot = path.join(dir, '.memoc', 'worklog');
|
|
3431
|
+
const recent = listMarkdownFiles(workRoot)
|
|
3432
|
+
.filter(fp => path.basename(fp) !== 'README.md')
|
|
3433
|
+
.sort()
|
|
3434
|
+
.reverse()
|
|
3435
|
+
.slice(0, 20);
|
|
3436
|
+
writeActivityIndexes(dir, recent);
|
|
3437
|
+
mark('update', '.memoc/activity.md, .memoc/worklog/README.md, .memoc/actors/README.md');
|
|
3438
|
+
|
|
3439
|
+
ensureObsidianFrontmatter(dir, mark);
|
|
3440
|
+
|
|
3441
|
+
const after = memoryTokenStats(dir);
|
|
3442
|
+
const startupDelta = before.startupTokens - after.startupTokens;
|
|
3443
|
+
const allDelta = before.allTokens - after.allTokens;
|
|
3444
|
+
console.log(actions.join('\n'));
|
|
3445
|
+
console.log(`\n Startup ~${before.startupTokens} -> ~${after.startupTokens} tokens (${startupDelta >= 0 ? '-' : '+'}${Math.abs(startupDelta)})`);
|
|
3446
|
+
console.log(` All mem ~${before.allTokens} -> ~${after.allTokens} tokens (${allDelta >= 0 ? '-' : '+'}${Math.abs(allDelta)})`);
|
|
3447
|
+
console.log(' Note Decisions, handoff, rules, wiki topics, and source docs are preserved.');
|
|
3438
3448
|
console.log('\n Done.\n');
|
|
3439
3449
|
}
|
|
3440
3450
|
|
|
@@ -3470,8 +3480,8 @@ function compactSessionSummary(src) {
|
|
|
3470
3480
|
const lines = [
|
|
3471
3481
|
'# Session Summary',
|
|
3472
3482
|
`Last: ${nowISO()}`,
|
|
3473
|
-
'Replace
|
|
3474
|
-
'
|
|
3483
|
+
'Replace, do not append. Keep <800B.',
|
|
3484
|
+
'History: worklog. Resume risks: 04-handoff.md.',
|
|
3475
3485
|
'',
|
|
3476
3486
|
];
|
|
3477
3487
|
|
|
@@ -3493,14 +3503,21 @@ function sectionText(src, heading) {
|
|
|
3493
3503
|
}
|
|
3494
3504
|
|
|
3495
3505
|
function compactSummaryBullets(text) {
|
|
3506
|
+
const maxLine = 48;
|
|
3496
3507
|
return String(text || '')
|
|
3497
3508
|
.split(/\r?\n/)
|
|
3498
3509
|
.map(line => line.trim())
|
|
3499
3510
|
.filter(line => line && !line.startsWith('#') && !/^_.*_$/.test(line))
|
|
3500
3511
|
.map(line => line.replace(/^[-*]\s+/, '').replace(/^\d+[.)]\s+/, '').trim())
|
|
3501
3512
|
.filter(Boolean)
|
|
3502
|
-
.slice(0,
|
|
3503
|
-
.map(line =>
|
|
3513
|
+
.slice(0, 2)
|
|
3514
|
+
.map(line => {
|
|
3515
|
+
const compact = line
|
|
3516
|
+
.replace(/`/g, '')
|
|
3517
|
+
.replace(/\s+/g, ' ')
|
|
3518
|
+
.trim();
|
|
3519
|
+
return `- ${compact.length > maxLine ? `${compact.slice(0, maxLine - 3)}...` : compact}`;
|
|
3520
|
+
});
|
|
3504
3521
|
}
|
|
3505
3522
|
|
|
3506
3523
|
function summaryPlaceholder(heading) {
|
|
@@ -3585,7 +3602,7 @@ if (!cmd || cmd === '--help' || cmd === '-h' || cmd === 'help') {
|
|
|
3585
3602
|
console.log(' summary Print a tiny status/resume overview');
|
|
3586
3603
|
console.log(' tokens Estimate token cost of current memory files');
|
|
3587
3604
|
console.log(' trim-summary Archive and compact oversized session-summary.md');
|
|
3588
|
-
console.log(' compress
|
|
3605
|
+
console.log(' compress Compact memoc files and refresh generated indexes');
|
|
3589
3606
|
console.log(' add <agent> Add entry file for a specific agent (run without args to list)');
|
|
3590
3607
|
console.log(' actor [set <name>] Show or set the local memoc actor');
|
|
3591
3608
|
console.log(' work "<title>" Create a conflict-light actor worklog entry');
|