@kevin0181/memoc 1.1.10 → 1.2.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 +2 -2
- package/bin/cli.js +133 -29
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -144,7 +144,7 @@ Run it from the project root. It preserves existing project memory, including:
|
|
|
144
144
|
- `.memoc/systems/`
|
|
145
145
|
- `.memoc/wiki/`
|
|
146
146
|
|
|
147
|
-
It refreshes the managed blocks, project-local wrappers, runtime copy, PATH helpers, and
|
|
147
|
+
It refreshes the managed blocks, project-local wrappers, runtime copy, PATH helpers, and memoc-owned protocol templates. User-owned memory files such as `session-summary.md`, `03-decisions.md`, `04-handoff.md`, `06-project-rules.md`, and wiki topic/source pages are preserved. Upgrade also runs the `trim-summary` compaction pass so startup memory stays small. If `memoc` is not on PATH after upgrading, keep using:
|
|
148
148
|
|
|
149
149
|
```bash
|
|
150
150
|
# Windows
|
|
@@ -277,7 +277,7 @@ Actor detection order:
|
|
|
277
277
|
|
|
278
278
|
`activity.md`, `actors/README.md`, and `worklog/README.md` are regenerated indexes. Run `memoc activity --write` when you want to refresh them from worklog files.
|
|
279
279
|
|
|
280
|
-
`log.md` is legacy. New installs do not create it, and shared activity should live in worklog files.
|
|
280
|
+
`log.md` is legacy. New installs do not create it, and shared activity should live in worklog files. On upgrade, an existing `.memoc/log.md` is moved to `.memoc/raw/legacy-log.md` so old history is preserved but no longer part of the normal memory flow.
|
|
281
281
|
|
|
282
282
|
---
|
|
283
283
|
|
package/bin/cli.js
CHANGED
|
@@ -209,6 +209,12 @@ function write(filePath, content) {
|
|
|
209
209
|
fs.writeFileSync(filePath, content, 'utf8');
|
|
210
210
|
}
|
|
211
211
|
|
|
212
|
+
function writeChanged(filePath, content) {
|
|
213
|
+
if (fs.existsSync(filePath) && fs.readFileSync(filePath, 'utf8') === content) return false;
|
|
214
|
+
write(filePath, content);
|
|
215
|
+
return true;
|
|
216
|
+
}
|
|
217
|
+
|
|
212
218
|
function slugify(value, fallback = 'note') {
|
|
213
219
|
const slug = String(value || '')
|
|
214
220
|
.toLowerCase()
|
|
@@ -228,6 +234,76 @@ function uniquePath(filePath) {
|
|
|
228
234
|
return `${base}-${i}${ext}`;
|
|
229
235
|
}
|
|
230
236
|
|
|
237
|
+
function archiveLegacyLog(dir, mark) {
|
|
238
|
+
const logPath = path.join(dir, '.memoc', 'log.md');
|
|
239
|
+
if (!fs.existsSync(logPath)) {
|
|
240
|
+
mark('skip', '.memoc/log.md (legacy; no file)');
|
|
241
|
+
return;
|
|
242
|
+
}
|
|
243
|
+
const archivePath = uniquePath(path.join(dir, '.memoc', 'raw', 'legacy-log.md'));
|
|
244
|
+
fs.mkdirSync(path.dirname(archivePath), { recursive: true });
|
|
245
|
+
fs.renameSync(logPath, archivePath);
|
|
246
|
+
mark('move', `${path.relative(dir, logPath)} -> ${path.relative(dir, archivePath)}`);
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
function summarySectionBulletCounts(src) {
|
|
250
|
+
const counts = {};
|
|
251
|
+
let current = '';
|
|
252
|
+
for (const line of String(src || '').split(/\r?\n/)) {
|
|
253
|
+
const heading = line.match(/^##\s+(.+?)\s*$/);
|
|
254
|
+
if (heading) {
|
|
255
|
+
current = heading[1].trim();
|
|
256
|
+
counts[current] = counts[current] || 0;
|
|
257
|
+
continue;
|
|
258
|
+
}
|
|
259
|
+
if (current && /^-\s+/.test(line)) counts[current] = (counts[current] || 0) + 1;
|
|
260
|
+
}
|
|
261
|
+
return counts;
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
function trimSummaryFile(dir) {
|
|
265
|
+
const summaryPath = path.join(dir, '.memoc', 'session-summary.md');
|
|
266
|
+
const archivePath = path.join(dir, '.memoc', 'session-summary-archive.md');
|
|
267
|
+
if (!fs.existsSync(summaryPath)) {
|
|
268
|
+
write(summaryPath, tplSessionSummary());
|
|
269
|
+
return { action: 'add' };
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
const src = fs.readFileSync(summaryPath, 'utf8');
|
|
273
|
+
const beforeBytes = Buffer.byteLength(src, 'utf8');
|
|
274
|
+
const counts = summarySectionBulletCounts(src);
|
|
275
|
+
const tooManyBullets = Object.values(counts).some(count => count > 3);
|
|
276
|
+
if (beforeBytes <= 800 && !tooManyBullets) {
|
|
277
|
+
return { action: 'skip', beforeBytes };
|
|
278
|
+
}
|
|
279
|
+
|
|
280
|
+
const compact = compactSessionSummary(src);
|
|
281
|
+
const afterBytes = Buffer.byteLength(compact, 'utf8');
|
|
282
|
+
const archiveHeader = fs.existsSync(archivePath)
|
|
283
|
+
? ''
|
|
284
|
+
: '# Session Summary Archive\n\nOlder oversized startup summaries moved by `memoc trim-summary`.\n';
|
|
285
|
+
fs.appendFileSync(archivePath, `${archiveHeader}\n## [${nowISO()}] archived summary (${beforeBytes}B)\n\n${src.trimEnd()}\n`, 'utf8');
|
|
286
|
+
write(summaryPath, compact);
|
|
287
|
+
return { action: 'trim', beforeBytes, afterBytes };
|
|
288
|
+
}
|
|
289
|
+
|
|
290
|
+
function migrateLegacyLogReferences(filePath) {
|
|
291
|
+
if (!fs.existsSync(filePath)) return false;
|
|
292
|
+
const before = fs.readFileSync(filePath, 'utf8');
|
|
293
|
+
let after = before
|
|
294
|
+
.replace(/- \[Project Log\]\(log\.md\)\n/g, '- [Activity](activity.md)\n- [Worklog](worklog/README.md)\n')
|
|
295
|
+
.replace(/\| `\.memoc\/log\.md` \| For append-only history \|\n/g, '| `.memoc/activity.md` | Generated worklog index |\n| `.memoc/worklog/` | Actor-scoped work history |\n')
|
|
296
|
+
.replace(/See `\.memoc\/log\.md` for full history\./g, 'See `.memoc/worklog/` for full shared activity history.')
|
|
297
|
+
.replace(/See `\.memoc\/log\.md`\./g, 'See `.memoc/worklog/` and generated `.memoc/activity.md`.')
|
|
298
|
+
.replace(/- \[ \] `\.memoc\/log\.md` has a new entry for meaningful work\./g, '- [ ] Meaningful shared work has a `.memoc/worklog/<actor>/YYYY-MM/*.md` entry.')
|
|
299
|
+
.replace(/Append `\.memoc\/log\.md` for meaningful changes, decisions, and handoffs\./g, 'Create a short actor worklog with `memoc work "<title>" --from-git` for meaningful changes, decisions, and handoffs.')
|
|
300
|
+
.replace(/Keep completed history in `\.memoc\/log\.md`; keep current-state files short\./g, 'Keep completed history in actor worklogs; keep current-state files short.')
|
|
301
|
+
.replace(/Append `\.memoc\/log\.md`\./g, 'If the change is meaningful shared work, run `memoc work "<title>" --from-git`.');
|
|
302
|
+
if (after === before) return false;
|
|
303
|
+
write(filePath, after);
|
|
304
|
+
return true;
|
|
305
|
+
}
|
|
306
|
+
|
|
231
307
|
function markdownTitle(src, fallback) {
|
|
232
308
|
const m = String(src || '').match(/^#\s+(.+)$/m);
|
|
233
309
|
return m ? m[1].trim() : fallback;
|
|
@@ -2252,10 +2328,18 @@ function run(dir, forceUpdate, action = 'update') {
|
|
|
2252
2328
|
mark('add', 'llms.txt');
|
|
2253
2329
|
}
|
|
2254
2330
|
|
|
2255
|
-
//
|
|
2331
|
+
// Generated memory maps — replace so old protocols do not linger.
|
|
2332
|
+
const generatedRefresh = [
|
|
2333
|
+
[path.join(memDir, '00-project-brief.md'), () => tplProjectBrief(p)],
|
|
2334
|
+
[path.join(memDir, '00-agent-index.md'), () => tplAgentIndex(p)],
|
|
2335
|
+
];
|
|
2336
|
+
for (const [fp, tpl] of generatedRefresh) {
|
|
2337
|
+
const rel = path.relative(dir, fp);
|
|
2338
|
+
mark(writeChanged(fp, tpl()) ? 'update' : 'skip', rel);
|
|
2339
|
+
}
|
|
2340
|
+
|
|
2341
|
+
// Dynamic user-owned memory files — update managed sections only
|
|
2256
2342
|
const dynUpdates = [
|
|
2257
|
-
[path.join(memDir, '00-project-brief.md'), () => tplProjectBrief(p), ID_S, ID_E, identityInner(p)],
|
|
2258
|
-
[path.join(memDir, '00-agent-index.md'), () => tplAgentIndex(p), SNAP_S, SNAP_E, snapshotInner(p)],
|
|
2259
2343
|
[path.join(memDir, '02-current-project-state.md'), () => tplCurrentState(p), SNAP_S, SNAP_E, snapshotInner(p)],
|
|
2260
2344
|
];
|
|
2261
2345
|
for (const [fp, tpl, s, e, inner] of dynUpdates) {
|
|
@@ -2283,18 +2367,27 @@ function run(dir, forceUpdate, action = 'update') {
|
|
|
2283
2367
|
mark('add', '.memoc/session-summary.md');
|
|
2284
2368
|
}
|
|
2285
2369
|
|
|
2286
|
-
//
|
|
2287
|
-
const
|
|
2370
|
+
// Protocol/template files — replace on update so old instructions are removed.
|
|
2371
|
+
const templateRefresh = [
|
|
2288
2372
|
[path.join(memDir, 'boot.md'), tplBoot],
|
|
2289
2373
|
[path.join(memDir, '01-agent-workflow.md'), tplWorkflow],
|
|
2374
|
+
[path.join(memDir, '05-done-checklist.md'), tplDoneChecklist],
|
|
2375
|
+
[path.join(memDir, 'memoc-usage.md'), tplMemocUsage],
|
|
2376
|
+
[path.join(dir, 'skills/project-memory-maintainer/SKILL.md'), tplSkillMaintainer],
|
|
2377
|
+
];
|
|
2378
|
+
for (const [fp, tpl] of templateRefresh) {
|
|
2379
|
+
const rel = path.relative(dir, fp);
|
|
2380
|
+
mark(writeChanged(fp, tpl()) ? 'update' : 'skip', rel);
|
|
2381
|
+
}
|
|
2382
|
+
|
|
2383
|
+
// Static indexes/scaffolds — add if missing; content may be user- or command-owned.
|
|
2384
|
+
const addIfMissing = [
|
|
2290
2385
|
[path.join(memDir, '03-decisions.md'), tplDecisions],
|
|
2291
2386
|
[path.join(memDir, '04-handoff.md'), tplHandoff],
|
|
2292
|
-
[path.join(memDir, '05-done-checklist.md'), tplDoneChecklist],
|
|
2293
2387
|
[path.join(memDir, '06-project-rules.md'), tplProjectRules],
|
|
2294
2388
|
[path.join(memDir, 'activity.md'), tplActivity],
|
|
2295
2389
|
[path.join(memDir, 'actors/README.md'), tplActorsReadme],
|
|
2296
2390
|
[path.join(memDir, 'worklog/README.md'), tplWorklogReadme],
|
|
2297
|
-
[path.join(memDir, 'memoc-usage.md'), tplMemocUsage],
|
|
2298
2391
|
[path.join(memDir, 'systems/README.md'), tplSystemsReadme],
|
|
2299
2392
|
[path.join(memDir, 'raw/README.md'), tplRawReadme],
|
|
2300
2393
|
[path.join(memDir, 'raw/files/README.md'), tplRawFilesReadme],
|
|
@@ -2309,7 +2402,6 @@ function run(dir, forceUpdate, action = 'update') {
|
|
|
2309
2402
|
[path.join(memDir, 'wiki/sources/README.md'), tplWikiSourcesReadme],
|
|
2310
2403
|
[path.join(memDir, 'wiki/topics/README.md'), tplWikiTopicsReadme],
|
|
2311
2404
|
[path.join(memDir, 'wiki/global/README.md'), tplWikiGlobalReadme],
|
|
2312
|
-
[path.join(dir, 'skills/project-memory-maintainer/SKILL.md'), tplSkillMaintainer],
|
|
2313
2405
|
];
|
|
2314
2406
|
for (const [fp, tpl] of addIfMissing) {
|
|
2315
2407
|
const rel = path.relative(dir, fp);
|
|
@@ -2318,8 +2410,20 @@ function run(dir, forceUpdate, action = 'update') {
|
|
|
2318
2410
|
}
|
|
2319
2411
|
ensureWikiScaffoldLinks(memDir, mark);
|
|
2320
2412
|
|
|
2321
|
-
|
|
2322
|
-
|
|
2413
|
+
const legacyReferenceFiles = [
|
|
2414
|
+
path.join(memDir, '02-current-project-state.md'),
|
|
2415
|
+
path.join(memDir, '04-handoff.md'),
|
|
2416
|
+
path.join(memDir, '06-project-rules.md'),
|
|
2417
|
+
path.join(memDir, 'systems/README.md'),
|
|
2418
|
+
path.join(memDir, 'wiki/index.md'),
|
|
2419
|
+
path.join(memDir, 'wiki/sources.md'),
|
|
2420
|
+
path.join(memDir, 'wiki/glossary.md'),
|
|
2421
|
+
path.join(memDir, 'wiki/questions.md'),
|
|
2422
|
+
path.join(memDir, 'wiki/lint.md'),
|
|
2423
|
+
];
|
|
2424
|
+
for (const fp of legacyReferenceFiles) {
|
|
2425
|
+
if (migrateLegacyLogReferences(fp)) mark('update', `${path.relative(dir, fp)} (legacy refs)`);
|
|
2426
|
+
}
|
|
2323
2427
|
|
|
2324
2428
|
// PATH helpers — let agents run memoc even when the npm bin is not on PATH
|
|
2325
2429
|
ensureClaudeStopHookFile(dir, mark);
|
|
@@ -2327,7 +2431,20 @@ function run(dir, forceUpdate, action = 'update') {
|
|
|
2327
2431
|
ensurePathHelpers(dir, mark);
|
|
2328
2432
|
ensurePathRegistration(dir, mark);
|
|
2329
2433
|
|
|
2330
|
-
|
|
2434
|
+
archiveLegacyLog(dir, mark);
|
|
2435
|
+
|
|
2436
|
+
const trim = trimSummaryFile(dir);
|
|
2437
|
+
if (trim.action === 'trim') {
|
|
2438
|
+
mark('update', `.memoc/session-summary.md (trimmed ${trim.beforeBytes}B -> ${trim.afterBytes}B)`);
|
|
2439
|
+
mark('update', '.memoc/session-summary-archive.md');
|
|
2440
|
+
} else if (trim.action === 'add') {
|
|
2441
|
+
mark('add', '.memoc/session-summary.md');
|
|
2442
|
+
} else {
|
|
2443
|
+
mark('skip', `.memoc/session-summary.md (compact ${trim.beforeBytes || 0}B)`);
|
|
2444
|
+
}
|
|
2445
|
+
|
|
2446
|
+
// Obsidian graph filters — add/merge memoc tags for existing installs too
|
|
2447
|
+
ensureObsidianFrontmatter(dir, mark);
|
|
2331
2448
|
}
|
|
2332
2449
|
|
|
2333
2450
|
hideOnWindows(memDir);
|
|
@@ -3326,37 +3443,24 @@ function runCompress(dir) {
|
|
|
3326
3443
|
// ═══════════════════════════════════════════════════════════════════
|
|
3327
3444
|
|
|
3328
3445
|
function runTrimSummary(dir) {
|
|
3329
|
-
const
|
|
3330
|
-
|
|
3331
|
-
if (!fs.existsSync(summaryPath)) {
|
|
3332
|
-
write(summaryPath, tplSessionSummary());
|
|
3446
|
+
const result = trimSummaryFile(dir);
|
|
3447
|
+
if (result.action === 'add') {
|
|
3333
3448
|
console.log('\n memoc trim-summary\n');
|
|
3334
3449
|
console.log(' Added .memoc/session-summary.md');
|
|
3335
3450
|
console.log('\n Done.\n');
|
|
3336
3451
|
return;
|
|
3337
3452
|
}
|
|
3338
3453
|
|
|
3339
|
-
|
|
3340
|
-
const beforeBytes = Buffer.byteLength(src, 'utf8');
|
|
3341
|
-
const compact = compactSessionSummary(src);
|
|
3342
|
-
const afterBytes = Buffer.byteLength(compact, 'utf8');
|
|
3343
|
-
|
|
3344
|
-
if (src === compact && beforeBytes <= 800) {
|
|
3454
|
+
if (result.action === 'skip') {
|
|
3345
3455
|
console.log('\n memoc trim-summary\n');
|
|
3346
|
-
console.log(` session-summary.md is already compact (${beforeBytes}B).`);
|
|
3456
|
+
console.log(` session-summary.md is already compact (${result.beforeBytes}B).`);
|
|
3347
3457
|
console.log('\n Done.\n');
|
|
3348
3458
|
return;
|
|
3349
3459
|
}
|
|
3350
3460
|
|
|
3351
|
-
const archiveHeader = fs.existsSync(archivePath)
|
|
3352
|
-
? ''
|
|
3353
|
-
: '# Session Summary Archive\n\nOlder oversized startup summaries moved by `memoc trim-summary`.\n';
|
|
3354
|
-
fs.appendFileSync(archivePath, `${archiveHeader}\n## [${nowISO()}] archived summary (${beforeBytes}B)\n\n${src.trimEnd()}\n`, 'utf8');
|
|
3355
|
-
write(summaryPath, compact);
|
|
3356
|
-
|
|
3357
3461
|
console.log('\n memoc trim-summary\n');
|
|
3358
3462
|
console.log(` Archived .memoc/session-summary-archive.md`);
|
|
3359
|
-
console.log(` Rewrote .memoc/session-summary.md (${beforeBytes}B → ${afterBytes}B)`);
|
|
3463
|
+
console.log(` Rewrote .memoc/session-summary.md (${result.beforeBytes}B → ${result.afterBytes}B)`);
|
|
3360
3464
|
console.log(' Reminder Completed history belongs in worklog; resume details belong in 04-handoff.md.');
|
|
3361
3465
|
console.log('\n Done.\n');
|
|
3362
3466
|
}
|