@kevin0181/memoc 1.2.1 → 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 +78 -70
- 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
|
@@ -3294,51 +3294,29 @@ function searchPriority(file, scope = 'memory') {
|
|
|
3294
3294
|
// ═══════════════════════════════════════════════════════════════════
|
|
3295
3295
|
|
|
3296
3296
|
function runTokens(dir) {
|
|
3297
|
-
const
|
|
3298
|
-
const read = fp => { try { return fs.readFileSync(fp, 'utf8'); } catch { return ''; } };
|
|
3299
|
-
const memDir = path.join(dir, '.memoc');
|
|
3300
|
-
|
|
3301
|
-
const startup = [
|
|
3302
|
-
['CLAUDE.md', path.join(dir, 'CLAUDE.md')],
|
|
3303
|
-
['session-summary.md', path.join(memDir, 'session-summary.md')],
|
|
3304
|
-
];
|
|
3305
|
-
const onDemand = [
|
|
3306
|
-
['llms.txt', path.join(dir, 'llms.txt')],
|
|
3307
|
-
['02-current-project-state.md', path.join(memDir, '02-current-project-state.md')],
|
|
3308
|
-
['03-decisions.md', path.join(memDir, '03-decisions.md')],
|
|
3309
|
-
['04-handoff.md', path.join(memDir, '04-handoff.md')],
|
|
3310
|
-
['06-project-rules.md', path.join(memDir, '06-project-rules.md')],
|
|
3311
|
-
['activity.md', path.join(memDir, 'activity.md')],
|
|
3312
|
-
];
|
|
3297
|
+
const stats = memoryTokenStats(dir);
|
|
3313
3298
|
|
|
3314
3299
|
console.log('\n memoc tokens\n');
|
|
3315
3300
|
let startupTotal = 0;
|
|
3316
3301
|
console.log(' Startup (always loaded):');
|
|
3317
|
-
for (const
|
|
3318
|
-
|
|
3319
|
-
const
|
|
3320
|
-
|
|
3321
|
-
startupTotal += t;
|
|
3322
|
-
const warn = b > 1000 ? ' ⚠ large' : '';
|
|
3323
|
-
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}`);
|
|
3324
3306
|
}
|
|
3325
3307
|
console.log(` ${'── startup total'.padEnd(32)} ${String(startupTotal).padStart(5)} tokens`);
|
|
3326
3308
|
|
|
3327
3309
|
console.log('\n On-demand (read when needed):');
|
|
3328
3310
|
let onDemandTotal = 0;
|
|
3329
|
-
for (const
|
|
3330
|
-
|
|
3331
|
-
|
|
3332
|
-
|
|
3333
|
-
const b = Buffer.byteLength(content, 'utf8');
|
|
3334
|
-
onDemandTotal += t;
|
|
3335
|
-
const warn = t > 500 ? ' ⚠ consider compress' : '';
|
|
3336
|
-
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}`);
|
|
3337
3315
|
}
|
|
3338
3316
|
console.log(` ${'── on-demand total'.padEnd(32)} ${String(onDemandTotal).padStart(5)} tokens`);
|
|
3339
3317
|
console.log(`\n If all loaded: ~${startupTotal + onDemandTotal} tokens`);
|
|
3340
3318
|
|
|
3341
|
-
const summaryContent =
|
|
3319
|
+
const summaryContent = safeRead(path.join(dir, '.memoc', 'session-summary.md'));
|
|
3342
3320
|
const summaryBytes = Buffer.byteLength(summaryContent, 'utf8');
|
|
3343
3321
|
if (summaryBytes > 800) {
|
|
3344
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.`);
|
|
@@ -3346,6 +3324,37 @@ function runTokens(dir) {
|
|
|
3346
3324
|
console.log();
|
|
3347
3325
|
}
|
|
3348
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
|
+
|
|
3349
3358
|
// ═══════════════════════════════════════════════════════════════════
|
|
3350
3359
|
// DOCTOR — quick health checks for shared memoc repos
|
|
3351
3360
|
// ═══════════════════════════════════════════════════════════════════
|
|
@@ -3396,47 +3405,46 @@ function runDoctor(dir) {
|
|
|
3396
3405
|
}
|
|
3397
3406
|
|
|
3398
3407
|
// ═══════════════════════════════════════════════════════════════════
|
|
3399
|
-
// COMPRESS —
|
|
3408
|
+
// COMPRESS — safe whole-memory compaction
|
|
3400
3409
|
// ═══════════════════════════════════════════════════════════════════
|
|
3401
3410
|
|
|
3402
3411
|
function runCompress(dir) {
|
|
3403
|
-
|
|
3404
|
-
const
|
|
3405
|
-
const
|
|
3406
|
-
|
|
3407
|
-
if (!fs.existsSync(logPath)) {
|
|
3408
|
-
console.log('\n No .memoc/log.md found.\n');
|
|
3409
|
-
return;
|
|
3410
|
-
}
|
|
3411
|
-
|
|
3412
|
-
const src = fs.readFileSync(logPath, 'utf8');
|
|
3413
|
-
// Split on entry headers, keep header as part of each chunk
|
|
3414
|
-
const parts = src.split(/(?=\n## \[)/);
|
|
3415
|
-
const header = parts[0]; // everything before first entry
|
|
3416
|
-
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}`);
|
|
3417
3416
|
|
|
3418
|
-
|
|
3419
|
-
|
|
3420
|
-
|
|
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)`);
|
|
3421
3428
|
}
|
|
3422
3429
|
|
|
3423
|
-
const
|
|
3424
|
-
const
|
|
3425
|
-
|
|
3426
|
-
|
|
3427
|
-
|
|
3428
|
-
|
|
3429
|
-
|
|
3430
|
-
|
|
3431
|
-
|
|
3432
|
-
|
|
3433
|
-
|
|
3434
|
-
|
|
3435
|
-
|
|
3436
|
-
|
|
3437
|
-
console.log(
|
|
3438
|
-
|
|
3439
|
-
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.');
|
|
3440
3448
|
console.log('\n Done.\n');
|
|
3441
3449
|
}
|
|
3442
3450
|
|
|
@@ -3495,14 +3503,14 @@ function sectionText(src, heading) {
|
|
|
3495
3503
|
}
|
|
3496
3504
|
|
|
3497
3505
|
function compactSummaryBullets(text) {
|
|
3498
|
-
const maxLine =
|
|
3506
|
+
const maxLine = 48;
|
|
3499
3507
|
return String(text || '')
|
|
3500
3508
|
.split(/\r?\n/)
|
|
3501
3509
|
.map(line => line.trim())
|
|
3502
3510
|
.filter(line => line && !line.startsWith('#') && !/^_.*_$/.test(line))
|
|
3503
3511
|
.map(line => line.replace(/^[-*]\s+/, '').replace(/^\d+[.)]\s+/, '').trim())
|
|
3504
3512
|
.filter(Boolean)
|
|
3505
|
-
.slice(0,
|
|
3513
|
+
.slice(0, 2)
|
|
3506
3514
|
.map(line => {
|
|
3507
3515
|
const compact = line
|
|
3508
3516
|
.replace(/`/g, '')
|
|
@@ -3594,7 +3602,7 @@ if (!cmd || cmd === '--help' || cmd === '-h' || cmd === 'help') {
|
|
|
3594
3602
|
console.log(' summary Print a tiny status/resume overview');
|
|
3595
3603
|
console.log(' tokens Estimate token cost of current memory files');
|
|
3596
3604
|
console.log(' trim-summary Archive and compact oversized session-summary.md');
|
|
3597
|
-
console.log(' compress
|
|
3605
|
+
console.log(' compress Compact memoc files and refresh generated indexes');
|
|
3598
3606
|
console.log(' add <agent> Add entry file for a specific agent (run without args to list)');
|
|
3599
3607
|
console.log(' actor [set <name>] Show or set the local memoc actor');
|
|
3600
3608
|
console.log(' work "<title>" Create a conflict-light actor worklog entry');
|