@kevin0181/memoc 1.1.4 → 1.1.11

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.
Files changed (3) hide show
  1. package/README.md +40 -6
  2. package/bin/cli.js +668 -93
  3. package/package.json +1 -1
package/README.md CHANGED
@@ -82,6 +82,14 @@ npx @kevin0181/memoc init
82
82
  # Re-scan project and refresh managed sections
83
83
  npx @kevin0181/memoc update
84
84
 
85
+ # Shared repo activity tracking
86
+ npx @kevin0181/memoc actor
87
+ npx @kevin0181/memoc actor set neneee
88
+ npx @kevin0181/memoc work "Auth refresh fix" --from-git
89
+ npx @kevin0181/memoc activity
90
+ npx @kevin0181/memoc activity --write
91
+ npx @kevin0181/memoc doctor
92
+
85
93
  # Print current status in ~10 lines
86
94
  npx @kevin0181/memoc summary
87
95
 
@@ -105,7 +113,7 @@ npx @kevin0181/memoc tokens
105
113
  # Archive and compact an oversized startup summary
106
114
  npx @kevin0181/memoc trim-summary
107
115
 
108
- # Archive old log entries to keep log.md small
116
+ # Legacy: archive old log.md entries before deleting/migrating log.md
109
117
  npx @kevin0181/memoc compress
110
118
 
111
119
  # Add the same protocol to another agent's entry file
@@ -132,11 +140,11 @@ Run it from the project root. It preserves existing project memory, including:
132
140
  - `.memoc/03-decisions.md`
133
141
  - `.memoc/04-handoff.md`
134
142
  - `.memoc/06-project-rules.md`
135
- - `.memoc/log.md`
143
+ - Legacy `.memoc/log.md` if present
136
144
  - `.memoc/systems/`
137
145
  - `.memoc/wiki/`
138
146
 
139
- It refreshes the managed blocks, project-local wrappers, runtime copy, PATH helpers, and missing template files. If `memoc` is not on PATH after upgrading, keep using:
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. If `memoc` is not on PATH after upgrading, keep using:
140
148
 
141
149
  ```bash
142
150
  # Windows
@@ -164,7 +172,9 @@ llms.txt ← LLM-facing project map
164
172
  03-decisions.md ← Durable decision log
165
173
  04-handoff.md ← Resume context, verified/unverified
166
174
  06-project-rules.md ← User preferences
167
- log.md Append-only activity log
175
+ activity.md Short shared activity index
176
+ actors/ ← Actor profiles for shared repos
177
+ worklog/ ← Per-actor work records to reduce conflicts
168
178
  raw/ ← Immutable source material, not a startup read
169
179
  systems/ ← Subsystem docs
170
180
  wiki/ ← Synthesized knowledge base
@@ -182,10 +192,14 @@ Every entry file (`CLAUDE.md`, `AGENTS.md`, `.cursorrules`, etc.) gets the same
182
192
  ## Session Start
183
193
  - [ ] Read `.memoc/session-summary.md`
184
194
  - [ ] `.pending` exists? → review changed files → update memory if needed → delete it
195
+ - [ ] If `memoc` is not found, use the project-local wrapper.
185
196
 
186
197
  ## Before Opening More Files
187
198
  - [ ] Run `memoc search "<query>"` first
188
199
  - [ ] Open on demand: `02` status · `04` resume · `06` rules · `llms.txt` map
200
+ - [ ] Use `memoc grep "<query>"` only when memory is not enough.
201
+ - [ ] For durable source/wiki work, use `memoc ingest`, `memoc note`, and `memoc lint-wiki`.
202
+ - [ ] In shared repos, record meaningful work with `memoc work "<title>"`.
189
203
  - [ ] Keep output small: `summary`, `search --limit`, `search --snippets`
190
204
 
191
205
  ## Before Finishing _(update only applicable files; skip Q&A / throwaway exploration)_
@@ -194,6 +208,8 @@ Every entry file (`CLAUDE.md`, `AGENTS.md`, `.cursorrules`, etc.) gets the same
194
208
  - [ ] Work incomplete or risky → `04-handoff.md` (verified commands, unverified items, next steps)
195
209
  - [ ] Rule/preference set → `06-project-rules.md`
196
210
  - [ ] Wiki/systems work → read `skills/project-memory-maintainer/SKILL.md`
211
+ - [ ] Shared repo work → prefer `memoc work "<title>" --from-git`; run `memoc activity --write` only when regenerating indexes.
212
+ - [ ] Keep `session-summary.md` replace-only; completed work belongs in actor worklogs.
197
213
  ```
198
214
 
199
215
  The checklist tells agents exactly when to update, which file to update, and what to record — so nothing gets missed.
@@ -212,7 +228,7 @@ Startup cost is kept minimal by design.
212
228
 
213
229
  Everything else is on-demand. Use `memoc tokens` to see the live breakdown for your project.
214
230
 
215
- `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/log.md`, 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 trim-summary`; completed history belongs in `.memoc/worklog/<actor>/YYYY-MM/`, and unfinished/risky resume detail belongs in `.memoc/04-handoff.md`.
216
232
 
217
233
  ---
218
234
 
@@ -245,6 +261,24 @@ Add more agents on demand:
245
261
 
246
262
  Running `update` refreshes managed blocks in all existing agent files.
247
263
 
264
+ ## Shared Repos
265
+
266
+ Use `memoc work "<title>" --from-git` for meaningful work in shared repositories. It creates a new actor-scoped file under `.memoc/worklog/<actor>/YYYY-MM/`, prefills branch and changed files from git, and avoids append conflicts in shared files.
267
+
268
+ Actor detection order:
269
+
270
+ 1. `MEMOC_ACTOR`
271
+ 2. `.memoc/local/actor` from `memoc actor set <name>`
272
+ 3. `git config user.name`
273
+ 4. `git config user.email`
274
+ 5. OS username
275
+
276
+ `.memoc/local/` is ignored by git so each machine can keep its own actor setting.
277
+
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
+
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
+
248
282
  ---
249
283
 
250
284
  ## Supported Stacks
@@ -260,7 +294,7 @@ Node.js · Next.js · React · Vue · Svelte · Angular · Nuxt · Astro · Expr
260
294
  - **New project** — scaffolds all memory files with sensible defaults.
261
295
  - **Existing project** — detects your stack and fills in real project info (name, scripts, config files).
262
296
  - **Already initialized** — `init` injects the managed block without touching your existing content. `update` re-scans and refreshes project-specific sections.
263
- - **Long-running projects** — run `compress` to archive old `log.md` entries when the file grows large.
297
+ - **Long-running projects** — use actor worklogs for history; `compress` remains only for old `log.md` files.
264
298
 
265
299
  ---
266
300
 
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,21 +234,155 @@ 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 migrateLegacyLogReferences(filePath) {
250
+ if (!fs.existsSync(filePath)) return false;
251
+ const before = fs.readFileSync(filePath, 'utf8');
252
+ let after = before
253
+ .replace(/- \[Project Log\]\(log\.md\)\n/g, '- [Activity](activity.md)\n- [Worklog](worklog/README.md)\n')
254
+ .replace(/\| `\.memoc\/log\.md` \| For append-only history \|\n/g, '| `.memoc/activity.md` | Generated worklog index |\n| `.memoc/worklog/` | Actor-scoped work history |\n')
255
+ .replace(/See `\.memoc\/log\.md` for full history\./g, 'See `.memoc/worklog/` for full shared activity history.')
256
+ .replace(/See `\.memoc\/log\.md`\./g, 'See `.memoc/worklog/` and generated `.memoc/activity.md`.')
257
+ .replace(/- \[ \] `\.memoc\/log\.md` has a new entry for meaningful work\./g, '- [ ] Meaningful shared work has a `.memoc/worklog/<actor>/YYYY-MM/*.md` entry.')
258
+ .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.')
259
+ .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.')
260
+ .replace(/Append `\.memoc\/log\.md`\./g, 'If the change is meaningful shared work, run `memoc work "<title>" --from-git`.');
261
+ if (after === before) return false;
262
+ write(filePath, after);
263
+ return true;
264
+ }
265
+
231
266
  function markdownTitle(src, fallback) {
232
267
  const m = String(src || '').match(/^#\s+(.+)$/m);
233
268
  return m ? m[1].trim() : fallback;
234
269
  }
235
270
 
236
- function tplMemocCmdWrapper(cliPath = runtimeCliPath()) {
237
- return `@echo off\r\nnode "${escapeCmdPath(cliPath)}" %*\r\n`;
271
+ function execGitConfig(dir, key) {
272
+ try {
273
+ return require('child_process')
274
+ .execFileSync('git', ['config', '--get', key], { cwd: dir, encoding: 'utf8', stdio: ['ignore', 'pipe', 'ignore'] })
275
+ .trim();
276
+ } catch {
277
+ return '';
278
+ }
279
+ }
280
+
281
+ function actorFile(dir) {
282
+ return path.join(dir, '.memoc', 'local', 'actor');
283
+ }
284
+
285
+ function sanitizeActor(value) {
286
+ return slugify(String(value || '').replace(/@.*$/, ''), 'unknown');
287
+ }
288
+
289
+ function detectActor(dir) {
290
+ if (process.env.MEMOC_ACTOR) return { actor: sanitizeActor(process.env.MEMOC_ACTOR), source: 'MEMOC_ACTOR' };
291
+ const localPath = actorFile(dir);
292
+ try {
293
+ const local = fs.readFileSync(localPath, 'utf8').trim();
294
+ if (local) return { actor: sanitizeActor(local), source: '.memoc/local/actor' };
295
+ } catch {}
296
+ const gitUser = execGitConfig(dir, 'user.name');
297
+ if (gitUser) return { actor: sanitizeActor(gitUser), source: 'git config user.name' };
298
+ const gitEmail = execGitConfig(dir, 'user.email');
299
+ if (gitEmail) return { actor: sanitizeActor(gitEmail), source: 'git config user.email' };
300
+ const osUser = process.env.USER || process.env.USERNAME || process.env.LOGNAME;
301
+ if (osUser) return { actor: sanitizeActor(osUser), source: 'OS user' };
302
+ return { actor: 'unknown', source: 'fallback' };
238
303
  }
239
304
 
240
- function tplMemocPs1Wrapper(cliPath = runtimeCliPath()) {
241
- return `& node ${psSingleQuote(cliPath)} @args\nexit $LASTEXITCODE\n`;
305
+ function gitBranch(dir) {
306
+ try {
307
+ return require('child_process')
308
+ .execFileSync('git', ['branch', '--show-current'], { cwd: dir, encoding: 'utf8', stdio: ['ignore', 'pipe', 'ignore'] })
309
+ .trim() || 'unknown';
310
+ } catch {
311
+ return 'unknown';
312
+ }
242
313
  }
243
314
 
244
- function tplMemocShWrapper(cliPath = runtimeCliPath()) {
245
- return `#!/bin/sh\nexec node ${shellSingleQuote(cliPath)} "$@"\n`;
315
+ function gitStatusFiles(dir) {
316
+ try {
317
+ const out = require('child_process')
318
+ .execFileSync('git', ['status', '--short'], { cwd: dir, encoding: 'utf8', stdio: ['ignore', 'pipe', 'ignore'] })
319
+ .trim();
320
+ if (!out) return [];
321
+ return out.split(/\r?\n/)
322
+ .map(line => line.slice(3).trim())
323
+ .filter(Boolean)
324
+ .filter(file => !file.startsWith('.memoc/worklog/') && !file.startsWith('.memoc/activity.md'))
325
+ .slice(0, 12);
326
+ } catch {
327
+ return [];
328
+ }
329
+ }
330
+
331
+ function tplMemocCmdWrapper() {
332
+ return [
333
+ '@echo off',
334
+ 'set "MEMOC_RUNTIME=%MEMOC_RUNTIME_DIR%"',
335
+ 'if "%MEMOC_RUNTIME%"=="" (',
336
+ ' if not "%LOCALAPPDATA%"=="" (',
337
+ ' set "MEMOC_RUNTIME=%LOCALAPPDATA%\\memoc\\runtime"',
338
+ ' ) else (',
339
+ ' set "MEMOC_RUNTIME=%USERPROFILE%\\AppData\\Local\\memoc\\runtime"',
340
+ ' )',
341
+ ')',
342
+ 'set "MEMOC_CLI=%MEMOC_RUNTIME%\\bin\\cli.js"',
343
+ 'if exist "%MEMOC_CLI%" (',
344
+ ' node "%MEMOC_CLI%" %*',
345
+ ') else (',
346
+ ' npx @kevin0181/memoc@latest %*',
347
+ ')',
348
+ 'exit /b %ERRORLEVEL%',
349
+ '',
350
+ ].join('\r\n');
351
+ }
352
+
353
+ function tplMemocPs1Wrapper() {
354
+ return [
355
+ '$runtime = $env:MEMOC_RUNTIME_DIR',
356
+ 'if (-not $runtime) {',
357
+ ' if ($env:LOCALAPPDATA) { $runtime = Join-Path $env:LOCALAPPDATA "memoc\\runtime" }',
358
+ ' else { $runtime = Join-Path $env:USERPROFILE "AppData\\Local\\memoc\\runtime" }',
359
+ '}',
360
+ '$cli = Join-Path $runtime "bin\\cli.js"',
361
+ 'if (Test-Path $cli) {',
362
+ ' & node $cli @args',
363
+ '} else {',
364
+ ' & npx @kevin0181/memoc@latest @args',
365
+ '}',
366
+ 'exit $LASTEXITCODE',
367
+ '',
368
+ ].join('\n');
369
+ }
370
+
371
+ function tplMemocShWrapper() {
372
+ return [
373
+ '#!/bin/sh',
374
+ 'if [ -n "$MEMOC_RUNTIME_DIR" ]; then',
375
+ ' memoc_runtime="$MEMOC_RUNTIME_DIR"',
376
+ 'else',
377
+ ' memoc_runtime="${HOME:-$PWD}/.local/share/memoc/runtime"',
378
+ 'fi',
379
+ 'memoc_cli="$memoc_runtime/bin/cli.js"',
380
+ 'if [ -f "$memoc_cli" ]; then',
381
+ ' exec node "$memoc_cli" "$@"',
382
+ 'fi',
383
+ 'exec npx @kevin0181/memoc@latest "$@"',
384
+ '',
385
+ ].join('\n');
246
386
  }
247
387
 
248
388
  function defaultUserBinDir() {
@@ -274,11 +414,11 @@ function tplEnvSh() {
274
414
  }
275
415
 
276
416
  function ensurePathHelpers(dir, mark) {
277
- const cliPath = ensureRuntimeInstall(mark);
417
+ ensureRuntimeInstall(mark);
278
418
  const files = [
279
- [path.join(dir, '.memoc', 'bin', 'memoc.cmd'), () => tplMemocCmdWrapper(cliPath), false],
280
- [path.join(dir, '.memoc', 'bin', 'memoc.ps1'), () => tplMemocPs1Wrapper(cliPath), false],
281
- [path.join(dir, '.memoc', 'bin', 'memoc'), () => tplMemocShWrapper(cliPath), true],
419
+ [path.join(dir, '.memoc', 'bin', 'memoc.cmd'), tplMemocCmdWrapper, false],
420
+ [path.join(dir, '.memoc', 'bin', 'memoc.ps1'), tplMemocPs1Wrapper, false],
421
+ [path.join(dir, '.memoc', 'bin', 'memoc'), tplMemocShWrapper, true],
282
422
  [path.join(dir, '.memoc', 'env.ps1'), tplEnvPs1, false],
283
423
  [path.join(dir, '.memoc', 'env.sh'), tplEnvSh, true],
284
424
  ];
@@ -293,15 +433,16 @@ function ensurePathHelpers(dir, mark) {
293
433
 
294
434
  function ensureUserLauncher(mark) {
295
435
  const userBin = defaultUserBinDir();
296
- writeLaunchers(userBin, mark, 'user bin', ensureRuntimeInstall(mark));
436
+ ensureRuntimeInstall(mark);
437
+ writeLaunchers(userBin, mark, 'user bin');
297
438
  return userBin;
298
439
  }
299
440
 
300
- function writeLaunchers(binDir, mark, label, cliPath = ensureRuntimeInstall(mark)) {
441
+ function writeLaunchers(binDir, mark, label) {
301
442
  const files = [
302
- [path.join(binDir, 'memoc.cmd'), () => tplMemocCmdWrapper(cliPath), false],
303
- [path.join(binDir, 'memoc.ps1'), () => tplMemocPs1Wrapper(cliPath), false],
304
- [path.join(binDir, 'memoc'), () => tplMemocShWrapper(cliPath), true],
443
+ [path.join(binDir, 'memoc.cmd'), tplMemocCmdWrapper, false],
444
+ [path.join(binDir, 'memoc.ps1'), tplMemocPs1Wrapper, false],
445
+ [path.join(binDir, 'memoc'), tplMemocShWrapper, true],
305
446
  ];
306
447
 
307
448
  for (const [fp, tpl, executable] of files) {
@@ -398,7 +539,8 @@ function ensureCurrentPathLauncher(mark) {
398
539
  mark('skip', 'active PATH launcher (no writable PATH directory found)');
399
540
  return false;
400
541
  }
401
- writeLaunchers(target, mark, 'active PATH', ensureRuntimeInstall(mark));
542
+ ensureRuntimeInstall(mark);
543
+ writeLaunchers(target, mark, 'active PATH');
402
544
  return true;
403
545
  }
404
546
 
@@ -631,6 +773,7 @@ function managedBlock() {
631
773
  - [ ] If memory search is not enough, search project files with \`memoc grep "<query>" --limit 5\` (or wrapper fallback)
632
774
  - [ ] If asked to refresh/update memoc project memory, run \`memoc update\` first; this refreshes managed sections, wiki links, and Obsidian tags.
633
775
  - [ ] For durable source material use \`memoc ingest <path-or-url>\`; for durable analysis/query results use \`memoc note "<title>"\`; after wiki edits run \`memoc lint-wiki\`.
776
+ - [ ] In shared repos, record meaningful work with \`memoc work "<title>"\`; actor defaults to \`MEMOC_ACTOR\`, local actor, git user, git email, or OS user.
634
777
  - [ ] Keep output small: \`summary\`, \`search --limit\`, \`grep --limit\`, \`--snippets\`
635
778
 
636
779
  ## Before Finishing _(update only applicable files; skip Q&A / throwaway exploration)_
@@ -640,7 +783,8 @@ function managedBlock() {
640
783
  - [ ] Rule/preference set? Update \`06-project-rules.md\`
641
784
  - [ ] Wiki/systems work? Read \`skills/project-memory-maintainer/SKILL.md\`
642
785
  - [ ] User asked to update memoc/project memory? Run \`memoc update\`, then update the smallest relevant agent-owned memory files.
643
- - [ ] Keep \`session-summary.md\` as a replace-only snapshot under 800B; move completed history to \`log.md\` and resume details to \`04-handoff.md\`. If it grew, run \`memoc trim-summary\`.
786
+ - [ ] Shared repo work? Prefer \`memoc work "<title>" --from-git\` over appending shared files; run \`memoc activity --write\` only when regenerating indexes.
787
+ - [ ] Keep \`session-summary.md\` as a replace-only snapshot under 800B; move completed work to actor worklogs and resume risks to \`04-handoff.md\`. If it grew, run \`memoc trim-summary\`.
644
788
  ${MGMT_E}`;
645
789
  }
646
790
 
@@ -675,8 +819,8 @@ function coreLlmsInner() {
675
819
  - [Project Brief](.memoc/00-project-brief.md): short identity and direction.
676
820
  - [Workflow](.memoc/01-agent-workflow.md): update trigger matrix.
677
821
  - [Decisions](.memoc/03-decisions.md): durable decisions.
678
- - [Log](.memoc/log.md): append-only history.
679
822
  - [Systems](.memoc/systems/README.md): subsystem docs.
823
+ - [Activity](.memoc/activity.md): generated worklog index.
680
824
  - [Raw Sources](.memoc/raw/README.md): immutable source material; do not read by default.
681
825
  - [Wiki](.memoc/wiki/index.md): synthesized knowledge.`;
682
826
  }
@@ -848,6 +992,12 @@ function obsidianFrontmatterSpec(relPath) {
848
992
  } else if (rel.startsWith('.memoc/raw/')) {
849
993
  type = 'raw';
850
994
  tags.push('memoc/raw');
995
+ } else if (rel.startsWith('.memoc/worklog/')) {
996
+ type = 'worklog';
997
+ tags.push('memoc/worklog');
998
+ } else if (rel.startsWith('.memoc/actors/')) {
999
+ type = 'actor';
1000
+ tags.push('memoc/actor');
851
1001
  } else if (rel.startsWith('skills/project-memory-maintainer/')) {
852
1002
  type = 'skill';
853
1003
  tags.push('memoc/skill');
@@ -864,10 +1014,18 @@ function obsidianFrontmatterSpec(relPath) {
864
1014
  function mergeYamlFrontmatter(src, spec) {
865
1015
  const fm = parseYamlFrontmatter(src);
866
1016
  if (!fm) {
867
- return `${formatMemocFrontmatter(spec)}\n${src}`;
1017
+ return `${formatMemocFrontmatter(spec)}\n${String(src || '').replace(/^\uFEFF/, '')}`;
1018
+ }
1019
+
1020
+ let body = fm.body;
1021
+ let rest = String(src || '').slice(fm.end).replace(/^\uFEFF/, '');
1022
+ const nested = parseYamlFrontmatter(rest);
1023
+ if (nested) {
1024
+ body = `${nested.body}\n${body}`;
1025
+ rest = rest.slice(nested.end).replace(/^\uFEFF/, '');
868
1026
  }
869
1027
 
870
- const lines = fm.body.split(/\r?\n/);
1028
+ const lines = body.split(/\r?\n/);
871
1029
  const existingTags = readYamlTags(lines);
872
1030
  const mergedTags = [...new Set([...existingTags, ...spec.tags])];
873
1031
  const nextLines = mergeYamlScalar(lines, 'memoc', 'true');
@@ -880,14 +1038,16 @@ function mergeYamlFrontmatter(src, spec) {
880
1038
  mergeYamlTags(nextLines, mergedTags);
881
1039
 
882
1040
  const nextFm = ['---', ...nextLines, '---'].join('\n');
883
- return nextFm + src.slice(fm.end);
1041
+ return `${nextFm}\n${rest.replace(/^\r?\n/, '')}`;
884
1042
  }
885
1043
 
886
1044
  function parseYamlFrontmatter(src) {
887
- if (!src.startsWith('---\n') && !src.startsWith('---\r\n')) return null;
888
- const m = src.match(/^---\r?\n([\s\S]*?)\r?\n---\r?\n?/);
1045
+ const text = String(src || '').replace(/^\uFEFF/, '');
1046
+ const offset = text.length === src.length ? 0 : 1;
1047
+ if (!text.startsWith('---\n') && !text.startsWith('---\r\n')) return null;
1048
+ const m = text.match(/^---\r?\n([\s\S]*?)\r?\n---\r?\n?/);
889
1049
  if (!m) return null;
890
- return { body: m[1], end: m[0].length };
1050
+ return { body: m[1], end: m[0].length + offset };
891
1051
  }
892
1052
 
893
1053
  function formatMemocFrontmatter(spec) {
@@ -1097,7 +1257,9 @@ ${SNAP_E}
1097
1257
  - [Done Checklist](05-done-checklist.md)
1098
1258
  - [Project Rules](06-project-rules.md)
1099
1259
  - [Session Summary](session-summary.md)
1100
- - [Project Log](log.md)
1260
+ - [Activity](activity.md)
1261
+ - [Actors](actors/README.md)
1262
+ - [Worklog](worklog/README.md)
1101
1263
  - [Wiki Index](wiki/index.md)
1102
1264
  - [Raw Sources](raw/README.md)
1103
1265
  - [Systems Index](systems/README.md)
@@ -1137,7 +1299,7 @@ _None yet._
1137
1299
 
1138
1300
  ## Completed Tasks
1139
1301
 
1140
- See \`.memoc/log.md\` for full history.
1302
+ See \`.memoc/worklog/\` for full shared activity history.
1141
1303
 
1142
1304
  ## Commands
1143
1305
 
@@ -1149,7 +1311,7 @@ _None yet._
1149
1311
 
1150
1312
  ## Change Log
1151
1313
 
1152
- See \`.memoc/log.md\`.
1314
+ See \`.memoc/worklog/\` and generated \`.memoc/activity.md\`.
1153
1315
  `;
1154
1316
  }
1155
1317
 
@@ -1157,7 +1319,7 @@ function tplSessionSummary() {
1157
1319
  return `# Session Summary
1158
1320
  Last: ${nowISO()}
1159
1321
  Replace this file instead of appending to it. Keep total size <800B and each section ≤3 bullets.
1160
- Completed history belongs in \`log.md\`; incomplete/risky resume detail belongs in \`04-handoff.md\`.
1322
+ Completed history belongs in actor worklogs; incomplete/risky resume detail belongs in \`04-handoff.md\`.
1161
1323
  Agent-owned — updated by you, not by \`memoc update\`.
1162
1324
 
1163
1325
  ## Status
@@ -1194,7 +1356,6 @@ On-demand reference only. The entry-file managed block is authoritative.
1194
1356
  | \`.memoc/01-agent-workflow.md\` | When update routing is unclear |
1195
1357
  | \`.memoc/05-done-checklist.md\` | Before finishing substantial work |
1196
1358
  | \`.memoc/03-decisions.md\` | When a durable decision was made |
1197
- | \`.memoc/log.md\` | For append-only history |
1198
1359
  | \`.memoc/memoc-usage.md\` | For command details |
1199
1360
  | \`.memoc/systems/*.md\` | Before touching a specific subsystem |
1200
1361
  | \`.memoc/wiki/*.md\` | For synthesized project knowledge |
@@ -1227,15 +1388,16 @@ Shared protocol for any coding agent.
1227
1388
  | Trigger | Update |
1228
1389
  | --- | --- |
1229
1390
  | User asks "update memoc", "refresh project memory", or similar | Run \`memoc update\` first, then update relevant agent-owned memory files |
1230
- | User creates or changes a requirement | \`02-current-project-state.md\`, \`06-project-rules.md\`, \`log.md\` |
1231
- | Code, config, data, or assets changed | \`02-current-project-state.md\`, relevant \`systems/*.md\`, \`log.md\` |
1391
+ | User creates or changes a requirement | \`02-current-project-state.md\`, \`06-project-rules.md\`, \`memoc work "<title>" --from-git\` |
1392
+ | Code, config, data, or assets changed | \`02-current-project-state.md\`, relevant \`systems/*.md\`, \`memoc work "<title>" --from-git\` |
1232
1393
  | Architecture or system behavior changed | relevant \`systems/*.md\`, \`03-decisions.md\` |
1233
1394
  | A decision should affect future agents | \`03-decisions.md\`, \`02-current-project-state.md\` |
1234
- | Work is substantial enough to resume later | \`04-handoff.md\`, \`02-current-project-state.md\`, \`log.md\` |
1395
+ | Work is substantial enough to resume later | \`04-handoff.md\`, \`02-current-project-state.md\`, \`memoc work "<title>" --from-git\` |
1235
1396
  | Durable knowledge was learned | \`wiki/*.md\`, \`wiki/index.md\` |
1236
1397
  | Source material should feed the wiki | \`memoc ingest <path-or-url>\`, then synthesize affected \`wiki/topics/*.md\` |
1237
1398
  | A useful query answer should persist | \`memoc note "<title>"\`, then link related sources/topics |
1238
- | \`session-summary.md\` exceeds 800B or starts accumulating history | Run \`memoc trim-summary\`; move history to \`log.md\`, resume details to \`04-handoff.md\` |
1399
+ | Shared repo work should be traceable | \`memoc work "<title>"\`; avoid appending long details to shared core files |
1400
+ | \`session-summary.md\` exceeds 800B or starts accumulating history | Run \`memoc trim-summary\`; move completed history to worklog, resume details to \`04-handoff.md\` |
1239
1401
 
1240
1402
  ## Usually No Update Needed
1241
1403
 
@@ -1250,7 +1412,7 @@ Shared protocol for any coding agent.
1250
1412
  - \`02-current-project-state.md\`: current status, tasks, commands, recent notes.
1251
1413
  - \`04-handoff.md\`: resume context, blockers, verified/unverified checks.
1252
1414
  - \`03-decisions.md\`: append durable decisions only.
1253
- - \`log.md\`: full history; keep bulky completed work here.
1415
+ - \`worklog/<actor>/YYYY-MM/*.md\`: actor-scoped append-by-new-file activity records for shared repos.
1254
1416
  - \`systems/*.md\` and \`wiki/*.md\`: on-demand durable knowledge.
1255
1417
  `;
1256
1418
  }
@@ -1322,7 +1484,7 @@ Run through this before saying substantial work is complete.
1322
1484
  - [ ] \`.memoc/02-current-project-state.md\` reflects the new status.
1323
1485
  - [ ] \`.memoc/03-decisions.md\` updated if a durable decision was made.
1324
1486
  - [ ] \`.memoc/04-handoff.md\` updated if work is incomplete or risky.
1325
- - [ ] \`.memoc/log.md\` has a new entry for meaningful work.
1487
+ - [ ] Meaningful shared work has a \`.memoc/worklog/<actor>/YYYY-MM/*.md\` entry.
1326
1488
  - [ ] Relevant \`.memoc/systems/*.md\` or wiki pages updated.
1327
1489
 
1328
1490
  ## Communication
@@ -1347,7 +1509,7 @@ Durable user and project preferences live here. Update when the user gives a rul
1347
1509
  ## Agent Behavior Preferences
1348
1510
 
1349
1511
  - Be factual and operational in memory docs.
1350
- - Keep logs concise; do not paste temporary command output unless it changes future work.
1512
+ - Keep memory notes concise; do not paste temporary command output unless it changes future work.
1351
1513
  - Preserve user changes and avoid reverting unrelated work.
1352
1514
  - State unverified parts honestly in the final answer and handoff.
1353
1515
 
@@ -1360,9 +1522,75 @@ _None yet._
1360
1522
  function tplLog() {
1361
1523
  return `# Project Log
1362
1524
 
1363
- Append-only chronological log for project memory updates.
1525
+ Legacy file. New memoc activity belongs in actor worklogs under \`.memoc/worklog/\`.
1364
1526
 
1365
- ## [${nowISO()}] init | Initialized memoc memory structure.
1527
+ This file is no longer created for new projects. Existing projects may delete it after migrating useful entries to worklogs.
1528
+ `;
1529
+ }
1530
+
1531
+ function tplActivity() {
1532
+ return `# Activity
1533
+
1534
+ Shared activity index for memoc work logs.
1535
+
1536
+ ## How To Use
1537
+
1538
+ - Use \`memoc work "<title>"\` after meaningful work so shared repos get conflict-light per-actor records.
1539
+ - Keep this file short; detailed work belongs in [worklog](worklog/README.md).
1540
+ - Actor is detected from \`MEMOC_ACTOR\`, \`.memoc/local/actor\`, git config, git email, or OS user.
1541
+
1542
+ ## Recent Work
1543
+
1544
+ _None yet._
1545
+
1546
+ ## Related
1547
+
1548
+ - [Actors](actors/README.md)
1549
+ - [Worklog](worklog/README.md)
1550
+ `;
1551
+ }
1552
+
1553
+ function tplActorsReadme() {
1554
+ return `# Actors
1555
+
1556
+ People or agents that update memoc in this shared repo.
1557
+
1558
+ ## Actor Detection
1559
+
1560
+ 1. \`MEMOC_ACTOR\`
1561
+ 2. \`.memoc/local/actor\` set by \`memoc actor set <name>\`
1562
+ 3. \`git config user.name\`
1563
+ 4. \`git config user.email\`
1564
+ 5. OS username
1565
+
1566
+ \`.memoc/local/\` is ignored by git so each machine can keep its own actor setting.
1567
+
1568
+ ## Actors
1569
+
1570
+ _None yet. Use \`memoc actor set <name>\` or \`memoc work "<title>"\`._
1571
+ `;
1572
+ }
1573
+
1574
+ function tplWorklogReadme() {
1575
+ return `# Worklog
1576
+
1577
+ Conflict-light per-actor work records.
1578
+
1579
+ ## Rules
1580
+
1581
+ - Prefer creating new worklog files over appending shared core memory files.
1582
+ - Keep \`session-summary.md\` as the tiny current snapshot.
1583
+ - Put detailed completed work here; put only team-critical unfinished risk in \`04-handoff.md\`.
1584
+
1585
+ ## Layout
1586
+
1587
+ \`\`\`text
1588
+ worklog/<actor>/YYYY-MM/YYYYMMDDTHHMM-title.md
1589
+ \`\`\`
1590
+
1591
+ ## Recent Work
1592
+
1593
+ _None yet._
1366
1594
  `;
1367
1595
  }
1368
1596
 
@@ -1388,6 +1616,14 @@ memoc upgrade
1388
1616
  memoc update
1389
1617
  memoc trim-summary
1390
1618
 
1619
+ # Shared repo actor/work tracking
1620
+ memoc actor
1621
+ memoc actor set neneee
1622
+ memoc work "Auth refresh fix" --from-git
1623
+ memoc activity
1624
+ memoc activity --write
1625
+ memoc doctor
1626
+
1391
1627
  # Tiny status overview
1392
1628
  memoc summary
1393
1629
 
@@ -1420,6 +1656,12 @@ Use \`memoc search\` for known concepts, changed areas, decisions, tasks, or han
1420
1656
 
1421
1657
  Raw files under \`.memoc/raw/\` are intentionally not part of normal memory search. Open them only through a linked source record when provenance is needed.
1422
1658
 
1659
+ ## Shared Repo Activity
1660
+
1661
+ Use \`memoc work "<title>" --from-git\` to create conflict-light activity records under \`.memoc/worklog/<actor>/YYYY-MM/\`. The command prefills actor, branch, timestamp, and changed files from git so agents only need to fill short Summary/Verification notes when useful. Actor is detected in this order: \`MEMOC_ACTOR\`, \`.memoc/local/actor\`, \`git config user.name\`, \`git config user.email\`, OS username. Use \`memoc actor set <name>\` to store a local actor name without committing it.
1662
+
1663
+ \`.memoc/activity.md\`, \`.memoc/worklog/README.md\`, and \`.memoc/actors/README.md\` are regenerated indexes. Run \`memoc activity --write\` to rebuild them from worklog/actor files instead of appending to them during every task.
1664
+
1423
1665
  ## When To Run Memory Updates
1424
1666
 
1425
1667
  Use \`memoc update\` or \`skills/project-memory-maintainer/SKILL.md\` when:
@@ -1431,12 +1673,13 @@ Use \`memoc update\` or \`skills/project-memory-maintainer/SKILL.md\` when:
1431
1673
  - A decision was made that future agents should not revisit blindly.
1432
1674
  - Work is partial, multi-step, blocked, or likely to be resumed by another agent.
1433
1675
  - New durable knowledge belongs in \`.memoc/wiki/\` or a subsystem doc.
1676
+ - Shared work should be traceable without causing conflicts.
1434
1677
 
1435
1678
  Usually skip for pure Q&A, throwaway exploration, or tiny edits with no future impact.
1436
1679
 
1437
- When the user asks for a general memoc/project-memory refresh, run \`memoc update\` first. It refreshes managed sections, reconnects default wiki scaffold links, and applies Obsidian frontmatter tags. Then update only the agent-owned files whose content actually changed, such as \`.memoc/session-summary.md\`, \`.memoc/02-current-project-state.md\`, \`.memoc/04-handoff.md\`, \`.memoc/wiki/index.md\`, or \`.memoc/log.md\`.
1680
+ When the user asks for a general memoc/project-memory refresh, run \`memoc update\` first. It refreshes managed sections, reconnects default wiki scaffold links, and applies Obsidian frontmatter tags. Then update only the agent-owned files whose content actually changed, such as \`.memoc/session-summary.md\`, \`.memoc/02-current-project-state.md\`, \`.memoc/04-handoff.md\`, \`.memoc/wiki/index.md\`, or actor worklogs.
1438
1681
 
1439
- \`.memoc/session-summary.md\` is a startup snapshot, not a timeline. Rewrite it in place, do not append old work. If it exceeds 800B, run \`memoc trim-summary\`; it archives the previous summary and rewrites a compact version. Put completed history in \`.memoc/log.md\`, and put unfinished/risky resume detail in \`.memoc/04-handoff.md\`.
1682
+ \`.memoc/session-summary.md\` is a startup snapshot, not a timeline. Rewrite it in place, do not append old work. If it exceeds 800B, run \`memoc trim-summary\`; it archives the previous summary and rewrites a compact version. Put completed history in actor worklogs, and put unfinished/risky resume detail in \`.memoc/04-handoff.md\`.
1440
1683
 
1441
1684
  ## Updating The Wiki
1442
1685
 
@@ -1450,7 +1693,7 @@ Create a new Markdown file under \`.memoc/wiki/\` when synthesized knowledge sho
1450
1693
  After creating or editing wiki pages:
1451
1694
  1. Update \`.memoc/wiki/index.md\`.
1452
1695
  2. Run \`memoc lint-wiki\`.
1453
- 3. Append \`.memoc/log.md\`.
1696
+ 3. If the change is meaningful shared work, run \`memoc work "<title>" --from-git\`.
1454
1697
 
1455
1698
  Useful scaffolds:
1456
1699
 
@@ -1590,7 +1833,6 @@ _None yet. Use \`memoc note "<title>"\` for durable analysis or query results th
1590
1833
  - [Agent Index](../00-agent-index.md)
1591
1834
  - [Project Brief](../00-project-brief.md)
1592
1835
  - [Current Project State](../02-current-project-state.md)
1593
- - [Project Log](../log.md)
1594
1836
  `;
1595
1837
  }
1596
1838
 
@@ -1774,14 +2016,16 @@ Use this local skill after meaningful project work so future agents can continue
1774
2016
  - Update \`.memoc/04-handoff.md\` before ending substantial work.
1775
2017
  - Check \`.memoc/05-done-checklist.md\` before saying substantial work is complete.
1776
2018
  - Update \`.memoc/06-project-rules.md\` when the user gives durable preferences.
1777
- - Append \`.memoc/log.md\` for meaningful changes, decisions, and handoffs.
2019
+ - Create a short actor worklog with \`memoc work "<title>" --from-git\` for meaningful changes, decisions, and handoffs.
1778
2020
  - Create or update \`.memoc/systems/*.md\` when a subsystem needs durable explanation.
1779
2021
  - Create or update \`.memoc/wiki/*.md\` when synthesized knowledge should compound over time.
1780
2022
  - Use \`memoc ingest <path-or-url>\` for source material and \`memoc note "<title>"\` for durable query results or analysis.
2023
+ - Use \`memoc work "<title>" --from-git\` for meaningful shared-repo work so details are saved in actor-scoped worklog files instead of causing shared-file conflicts.
1781
2024
  - Keep the wiki graph connected: update \`.memoc/wiki/index.md\`, add relative Markdown links between related pages, and include a \`## Related\` section on every new wiki page.
1782
2025
  - Run \`memoc lint-wiki\` after wiki/source/topic edits and address broken links before finishing.
1783
- - Keep completed history in \`.memoc/log.md\`; keep current-state files short.
1784
- - Move completed session details out of \`session-summary.md\` into \`log.md\`; move incomplete/risky resume details into \`04-handoff.md\`.
2026
+ - Keep completed history in actor worklogs; keep current-state files short.
2027
+ - Move completed session details out of \`session-summary.md\` into \`.memoc/worklog/<actor>/YYYY-MM/\`; move incomplete/risky resume details into \`04-handoff.md\`.
2028
+ - In shared repos, do not use \`log.md\`; prefer new files under \`.memoc/worklog/<actor>/YYYY-MM/\` and regenerate \`.memoc/activity.md\` with \`memoc activity --write\`.
1785
2029
  - Keep tool output small; prefer \`summary\`, file-only search, \`--limit\`, and targeted reads.
1786
2030
 
1787
2031
  ## Wiki Link Rules
@@ -1890,17 +2134,16 @@ function ensureClaudeStopHookFile(dir, mark) {
1890
2134
 
1891
2135
  function ensurePendingGitignore(dir, mark) {
1892
2136
  const gitignorePath = path.join(dir, '.gitignore');
1893
- const PENDING_ENTRY = '.memoc/.pending';
2137
+ const entries = ['.memoc/.pending', '.memoc/local/'];
1894
2138
  const gitignoreContent = fs.existsSync(gitignorePath)
1895
2139
  ? fs.readFileSync(gitignorePath, 'utf8') : '';
1896
- const hasPendingEntry = gitignoreContent
1897
- .split(/\r?\n/)
1898
- .some(line => line.trim() === PENDING_ENTRY);
1899
- if (!hasPendingEntry) {
1900
- fs.appendFileSync(gitignorePath, (gitignoreContent.endsWith('\n') ? '' : '\n') + PENDING_ENTRY + '\n', 'utf8');
1901
- mark('update', '.gitignore (.memoc/.pending added)');
2140
+ const lines = gitignoreContent.split(/\r?\n/).map(line => line.trim());
2141
+ const missing = entries.filter(entry => !lines.includes(entry));
2142
+ if (missing.length) {
2143
+ fs.appendFileSync(gitignorePath, (gitignoreContent.endsWith('\n') ? '' : '\n') + missing.join('\n') + '\n', 'utf8');
2144
+ mark('update', `.gitignore (${missing.join(', ')} added)`);
1902
2145
  } else {
1903
- mark('skip', '.gitignore (.memoc/.pending already present)');
2146
+ mark('skip', '.gitignore (memoc local entries already present)');
1904
2147
  }
1905
2148
  }
1906
2149
 
@@ -1975,7 +2218,9 @@ function run(dir, forceUpdate, action = 'update') {
1975
2218
  [path.join(memDir, '04-handoff.md'), tplHandoff],
1976
2219
  [path.join(memDir, '05-done-checklist.md'), tplDoneChecklist],
1977
2220
  [path.join(memDir, '06-project-rules.md'), tplProjectRules],
1978
- [path.join(memDir, 'log.md'), tplLog],
2221
+ [path.join(memDir, 'activity.md'), tplActivity],
2222
+ [path.join(memDir, 'actors/README.md'), tplActorsReadme],
2223
+ [path.join(memDir, 'worklog/README.md'), tplWorklogReadme],
1979
2224
  [path.join(memDir, 'memoc-usage.md'), tplMemocUsage],
1980
2225
  [path.join(memDir, 'systems/README.md'), tplSystemsReadme],
1981
2226
  [path.join(memDir, 'raw/README.md'), tplRawReadme],
@@ -2042,10 +2287,18 @@ function run(dir, forceUpdate, action = 'update') {
2042
2287
  mark('add', 'llms.txt');
2043
2288
  }
2044
2289
 
2045
- // Dynamic memory filesupdate managed sections only
2290
+ // Generated memory mapsreplace so old protocols do not linger.
2291
+ const generatedRefresh = [
2292
+ [path.join(memDir, '00-project-brief.md'), () => tplProjectBrief(p)],
2293
+ [path.join(memDir, '00-agent-index.md'), () => tplAgentIndex(p)],
2294
+ ];
2295
+ for (const [fp, tpl] of generatedRefresh) {
2296
+ const rel = path.relative(dir, fp);
2297
+ mark(writeChanged(fp, tpl()) ? 'update' : 'skip', rel);
2298
+ }
2299
+
2300
+ // Dynamic user-owned memory files — update managed sections only
2046
2301
  const dynUpdates = [
2047
- [path.join(memDir, '00-project-brief.md'), () => tplProjectBrief(p), ID_S, ID_E, identityInner(p)],
2048
- [path.join(memDir, '00-agent-index.md'), () => tplAgentIndex(p), SNAP_S, SNAP_E, snapshotInner(p)],
2049
2302
  [path.join(memDir, '02-current-project-state.md'), () => tplCurrentState(p), SNAP_S, SNAP_E, snapshotInner(p)],
2050
2303
  ];
2051
2304
  for (const [fp, tpl, s, e, inner] of dynUpdates) {
@@ -2073,16 +2326,27 @@ function run(dir, forceUpdate, action = 'update') {
2073
2326
  mark('add', '.memoc/session-summary.md');
2074
2327
  }
2075
2328
 
2076
- // Static + user-owned files — only add if missing
2077
- const addIfMissing = [
2329
+ // Protocol/template files — replace on update so old instructions are removed.
2330
+ const templateRefresh = [
2078
2331
  [path.join(memDir, 'boot.md'), tplBoot],
2079
2332
  [path.join(memDir, '01-agent-workflow.md'), tplWorkflow],
2333
+ [path.join(memDir, '05-done-checklist.md'), tplDoneChecklist],
2334
+ [path.join(memDir, 'memoc-usage.md'), tplMemocUsage],
2335
+ [path.join(dir, 'skills/project-memory-maintainer/SKILL.md'), tplSkillMaintainer],
2336
+ ];
2337
+ for (const [fp, tpl] of templateRefresh) {
2338
+ const rel = path.relative(dir, fp);
2339
+ mark(writeChanged(fp, tpl()) ? 'update' : 'skip', rel);
2340
+ }
2341
+
2342
+ // Static indexes/scaffolds — add if missing; content may be user- or command-owned.
2343
+ const addIfMissing = [
2080
2344
  [path.join(memDir, '03-decisions.md'), tplDecisions],
2081
2345
  [path.join(memDir, '04-handoff.md'), tplHandoff],
2082
- [path.join(memDir, '05-done-checklist.md'), tplDoneChecklist],
2083
2346
  [path.join(memDir, '06-project-rules.md'), tplProjectRules],
2084
- [path.join(memDir, 'log.md'), tplLog],
2085
- [path.join(memDir, 'memoc-usage.md'), tplMemocUsage],
2347
+ [path.join(memDir, 'activity.md'), tplActivity],
2348
+ [path.join(memDir, 'actors/README.md'), tplActorsReadme],
2349
+ [path.join(memDir, 'worklog/README.md'), tplWorklogReadme],
2086
2350
  [path.join(memDir, 'systems/README.md'), tplSystemsReadme],
2087
2351
  [path.join(memDir, 'raw/README.md'), tplRawReadme],
2088
2352
  [path.join(memDir, 'raw/files/README.md'), tplRawFilesReadme],
@@ -2097,7 +2361,6 @@ function run(dir, forceUpdate, action = 'update') {
2097
2361
  [path.join(memDir, 'wiki/sources/README.md'), tplWikiSourcesReadme],
2098
2362
  [path.join(memDir, 'wiki/topics/README.md'), tplWikiTopicsReadme],
2099
2363
  [path.join(memDir, 'wiki/global/README.md'), tplWikiGlobalReadme],
2100
- [path.join(dir, 'skills/project-memory-maintainer/SKILL.md'), tplSkillMaintainer],
2101
2364
  ];
2102
2365
  for (const [fp, tpl] of addIfMissing) {
2103
2366
  const rel = path.relative(dir, fp);
@@ -2106,8 +2369,20 @@ function run(dir, forceUpdate, action = 'update') {
2106
2369
  }
2107
2370
  ensureWikiScaffoldLinks(memDir, mark);
2108
2371
 
2109
- // Obsidian graph filters — add/merge memoc tags for existing installs too
2110
- ensureObsidianFrontmatter(dir, mark);
2372
+ const legacyReferenceFiles = [
2373
+ path.join(memDir, '02-current-project-state.md'),
2374
+ path.join(memDir, '04-handoff.md'),
2375
+ path.join(memDir, '06-project-rules.md'),
2376
+ path.join(memDir, 'systems/README.md'),
2377
+ path.join(memDir, 'wiki/index.md'),
2378
+ path.join(memDir, 'wiki/sources.md'),
2379
+ path.join(memDir, 'wiki/glossary.md'),
2380
+ path.join(memDir, 'wiki/questions.md'),
2381
+ path.join(memDir, 'wiki/lint.md'),
2382
+ ];
2383
+ for (const fp of legacyReferenceFiles) {
2384
+ if (migrateLegacyLogReferences(fp)) mark('update', `${path.relative(dir, fp)} (legacy refs)`);
2385
+ }
2111
2386
 
2112
2387
  // PATH helpers — let agents run memoc even when the npm bin is not on PATH
2113
2388
  ensureClaudeStopHookFile(dir, mark);
@@ -2115,15 +2390,10 @@ function run(dir, forceUpdate, action = 'update') {
2115
2390
  ensurePathHelpers(dir, mark);
2116
2391
  ensurePathRegistration(dir, mark);
2117
2392
 
2118
- // Append update record to log.md
2119
- const logPath = path.join(memDir, 'log.md');
2120
- if (fs.existsSync(logPath)) {
2121
- fs.appendFileSync(logPath,
2122
- `\n## [${nowISO()}] ${action} | Re-scanned: ${p.isEmpty ? 'nothing detected' : stackStr(p.stack)}\n`,
2123
- 'utf8'
2124
- );
2125
- mark('append', '.memoc/log.md');
2126
- }
2393
+ archiveLegacyLog(dir, mark);
2394
+
2395
+ // Obsidian graph filters — add/merge memoc tags for existing installs too
2396
+ ensureObsidianFrontmatter(dir, mark);
2127
2397
  }
2128
2398
 
2129
2399
  hideOnWindows(memDir);
@@ -2162,6 +2432,262 @@ function runAdd(dir) {
2162
2432
  console.log('\n Done.');
2163
2433
  }
2164
2434
 
2435
+ // ═══════════════════════════════════════════════════════════════════
2436
+ // ACTOR / WORKLOG — conflict-light shared repo activity tracking
2437
+ // ═══════════════════════════════════════════════════════════════════
2438
+
2439
+ function runActor(dir) {
2440
+ const sub = (process.argv[3] || '').toLowerCase();
2441
+ if (sub === 'set') {
2442
+ const name = process.argv.slice(4).join(' ').trim();
2443
+ if (!name) {
2444
+ console.error('\n Usage: memoc actor set <name>');
2445
+ process.exit(1);
2446
+ }
2447
+ const actor = sanitizeActor(name);
2448
+ write(actorFile(dir), `${actor}\n`);
2449
+ ensurePendingGitignore(dir, () => {});
2450
+ console.log('\n memoc actor\n');
2451
+ console.log(` Set local actor: ${actor}`);
2452
+ console.log(' Stored in .memoc/local/actor (ignored by git).');
2453
+ console.log();
2454
+ return;
2455
+ }
2456
+
2457
+ const detected = detectActor(dir);
2458
+ console.log('\n memoc actor\n');
2459
+ console.log(` Actor ${detected.actor}`);
2460
+ console.log(` Source ${detected.source}`);
2461
+ console.log('\n Use `memoc actor set <name>` to override locally.\n');
2462
+ }
2463
+
2464
+ function runWork(dir) {
2465
+ const rawArgs = process.argv.slice(3);
2466
+ const opts = { status: 'done', body: '', fromGit: true };
2467
+ const titleParts = [];
2468
+ for (let i = 0; i < rawArgs.length; i++) {
2469
+ const arg = rawArgs[i];
2470
+ if (arg === '--status') {
2471
+ opts.status = sanitizeActor(rawArgs[++i] || 'done');
2472
+ continue;
2473
+ }
2474
+ if (arg.startsWith('--status=')) {
2475
+ opts.status = sanitizeActor(arg.slice('--status='.length) || 'done');
2476
+ continue;
2477
+ }
2478
+ if (arg === '--body') {
2479
+ opts.body = rawArgs.slice(i + 1).join(' ');
2480
+ break;
2481
+ }
2482
+ if (arg === '--from-git') {
2483
+ opts.fromGit = true;
2484
+ continue;
2485
+ }
2486
+ if (arg === '--no-git') {
2487
+ opts.fromGit = false;
2488
+ continue;
2489
+ }
2490
+ titleParts.push(arg);
2491
+ }
2492
+
2493
+ const title = titleParts.join(' ').trim();
2494
+ if (!title) {
2495
+ console.error('\n Usage: memoc work "<title>" [--status done|wip|blocked] [--from-git|--no-git] [--body "summary"]');
2496
+ process.exit(1);
2497
+ }
2498
+
2499
+ ensureMemocBase(dir);
2500
+ const detected = detectActor(dir);
2501
+ ensureActorProfile(dir, detected);
2502
+ const stamp = new Date().toISOString().slice(0, 16).replace(/[-:]/g, '').replace('T', 'T');
2503
+ const month = todayISO().slice(0, 7);
2504
+ const fileName = `${stamp}-${slugify(title, 'work')}.md`;
2505
+ const workPath = uniquePath(path.join(dir, '.memoc', 'worklog', detected.actor, month, fileName));
2506
+ write(workPath, worklogRecord(dir, title, detected, opts));
2507
+ ensureMemocFrontmatter(workPath, dir);
2508
+
2509
+ console.log('\n memoc work\n');
2510
+ console.log(` Actor ${detected.actor} (${detected.source})`);
2511
+ console.log(` Work ${normRel(dir, workPath)}`);
2512
+ console.log(' Next Fill only Summary/Verification if needed; run `memoc activity --write` to regenerate indexes.');
2513
+ console.log();
2514
+ }
2515
+
2516
+ function runActivity(dir) {
2517
+ const writeIndex = process.argv.slice(3).includes('--write');
2518
+ const workRoot = path.join(dir, '.memoc', 'worklog');
2519
+ const files = listMarkdownFiles(workRoot)
2520
+ .filter(fp => path.basename(fp) !== 'README.md')
2521
+ .sort()
2522
+ .reverse();
2523
+ const recent = files.slice(0, 20);
2524
+
2525
+ if (writeIndex) {
2526
+ writeActivityIndexes(dir, recent);
2527
+ ensureObsidianFrontmatter(dir, () => {});
2528
+ }
2529
+
2530
+ console.log('\n memoc activity\n');
2531
+ if (!recent.length) {
2532
+ console.log(' No worklog entries yet. Use `memoc work "<title>"`.');
2533
+ console.log();
2534
+ return;
2535
+ }
2536
+ for (const fp of recent) {
2537
+ const src = safeRead(fp);
2538
+ const title = markdownTitle(src, path.basename(fp, '.md'));
2539
+ const actor = (src.match(/^actor:\s*(.+)$/m) || [])[1] || 'unknown';
2540
+ const status = (src.match(/^status:\s*(.+)$/m) || [])[1] || 'unknown';
2541
+ console.log(` - ${normRel(dir, fp)} ${actor} ${status} ${title}`);
2542
+ }
2543
+ if (writeIndex) console.log('\n Wrote .memoc/activity.md and .memoc/worklog/README.md');
2544
+ console.log();
2545
+ }
2546
+
2547
+ function ensureActorProfile(dir, detected) {
2548
+ const actorPath = path.join(dir, '.memoc', 'actors', `${detected.actor}.md`);
2549
+ if (fs.existsSync(actorPath)) return;
2550
+ write(actorPath, actorProfile(detected));
2551
+ ensureMemocFrontmatter(actorPath, dir);
2552
+ }
2553
+
2554
+ function actorProfile(detected) {
2555
+ return `# ${detected.actor}
2556
+
2557
+ ## Identity
2558
+
2559
+ - Actor: ${detected.actor}
2560
+ - First detected from: ${detected.source}
2561
+ - First seen: ${nowISO()}
2562
+
2563
+ ## Notes
2564
+
2565
+ _Add stable collaboration preferences or ownership notes only when useful._
2566
+
2567
+ ## Related
2568
+
2569
+ - [Actors](README.md)
2570
+ - [Activity](../activity.md)
2571
+ - [Worklog](../worklog/README.md)
2572
+ `;
2573
+ }
2574
+
2575
+ function worklogRecord(dir, title, detected, opts) {
2576
+ const branch = gitBranch(dir);
2577
+ const changedFiles = opts.fromGit ? gitStatusFiles(dir) : [];
2578
+ return `# ${title}
2579
+
2580
+ actor: ${detected.actor}
2581
+ actor_source: ${detected.source}
2582
+ branch: ${branch}
2583
+ status: ${opts.status}
2584
+ created: ${nowISO()}
2585
+
2586
+ ## Summary
2587
+
2588
+ ${opts.body ? `- ${opts.body}` : '_1-3 bullets only. Keep this as a short receipt, not a report._'}
2589
+
2590
+ ## Changed Files
2591
+
2592
+ ${changedFiles.length ? changedFiles.map(file => `- \`${file}\``).join('\n') : '_None detected. Use `memoc work "<title>" --from-git` after editing files to prefill this section._'}
2593
+
2594
+ ## Verification
2595
+
2596
+ _Commands run or checks not run. Keep to 1-3 bullets._
2597
+
2598
+ ## Follow-up
2599
+
2600
+ _None._
2601
+
2602
+ ## Related
2603
+
2604
+ - [Activity](../../../activity.md)
2605
+ - [Worklog](../../README.md)
2606
+ - [Actor](../../../actors/${detected.actor}.md)
2607
+ `;
2608
+ }
2609
+
2610
+ function writeActivityIndexes(dir, recentFiles) {
2611
+ const activityPath = path.join(dir, '.memoc', 'activity.md');
2612
+ const worklogReadme = path.join(dir, '.memoc', 'worklog', 'README.md');
2613
+ const actorsReadme = path.join(dir, '.memoc', 'actors', 'README.md');
2614
+ const rows = recentFiles.map(fp => {
2615
+ const src = safeRead(fp);
2616
+ const title = markdownTitle(src, path.basename(fp, '.md'));
2617
+ const actor = (src.match(/^actor:\s*(.+)$/m) || [])[1] || 'unknown';
2618
+ const status = (src.match(/^status:\s*(.+)$/m) || [])[1] || 'unknown';
2619
+ return { fp, title, actor, status };
2620
+ });
2621
+ const activityItems = rows.length
2622
+ ? rows.map(row => `- [${row.title}](${pathRelativeMarkdown(path.join(dir, '.memoc'), row.fp).replace(/^\.\//, '')}) — ${row.actor} ${row.status}.`).join('\n')
2623
+ : '_None yet._';
2624
+ write(activityPath, `# Activity
2625
+
2626
+ Generated shared activity index for memoc work logs.
2627
+
2628
+ Last generated: ${nowISO()}
2629
+
2630
+ ## Recent Work
2631
+
2632
+ ${activityItems}
2633
+
2634
+ ## Related
2635
+
2636
+ - [Actors](actors/README.md)
2637
+ - [Worklog](worklog/README.md)
2638
+ `);
2639
+
2640
+ const worklogItems = rows.length
2641
+ ? rows.map(row => `- [${row.title}](${pathRelativeMarkdown(path.join(dir, '.memoc', 'worklog'), row.fp).replace(/^\.\//, '')}) — ${row.actor} ${row.status}.`).join('\n')
2642
+ : '_None yet._';
2643
+ write(worklogReadme, `# Worklog
2644
+
2645
+ Generated index of conflict-light per-actor work records.
2646
+
2647
+ Last generated: ${nowISO()}
2648
+
2649
+ ## Layout
2650
+
2651
+ \`\`\`text
2652
+ worklog/<actor>/YYYY-MM/YYYYMMDDTHHMM-title.md
2653
+ \`\`\`
2654
+
2655
+ ## Rules
2656
+
2657
+ - Prefer creating new worklog files over appending shared core memory files.
2658
+ - Keep worklog entries short: 1-3 summary bullets, key files, verification.
2659
+
2660
+ ## Recent Work
2661
+
2662
+ ${worklogItems}
2663
+ `);
2664
+
2665
+ const actorFiles = listMarkdownFiles(path.join(dir, '.memoc', 'actors'))
2666
+ .filter(fp => path.basename(fp) !== 'README.md')
2667
+ .sort();
2668
+ const actorItems = actorFiles.length
2669
+ ? actorFiles.map(fp => `- [${markdownTitle(safeRead(fp), path.basename(fp, '.md'))}](${path.basename(fp)})`).join('\n')
2670
+ : '_None yet. Use `memoc actor set <name>` or `memoc work "<title>"`._';
2671
+ write(actorsReadme, `# Actors
2672
+
2673
+ Generated actor index for this shared repo.
2674
+
2675
+ ## Actor Detection
2676
+
2677
+ 1. \`MEMOC_ACTOR\`
2678
+ 2. \`.memoc/local/actor\` set by \`memoc actor set <name>\`
2679
+ 3. \`git config user.name\`
2680
+ 4. \`git config user.email\`
2681
+ 5. OS username
2682
+
2683
+ \`.memoc/local/\` is ignored by git so each machine can keep its own actor setting.
2684
+
2685
+ ## Actors
2686
+
2687
+ ${actorItems}
2688
+ `);
2689
+ }
2690
+
2165
2691
  // ═══════════════════════════════════════════════════════════════════
2166
2692
  // WIKI OPERATIONS — lint, ingest, and durable topic notes
2167
2693
  // ═══════════════════════════════════════════════════════════════════
@@ -2285,8 +2811,6 @@ function runIngest(dir) {
2285
2811
  addWikiListItem(path.join(dir, '.memoc', 'wiki', 'sources.md'), 'Source Records', pathRelativeMarkdown(path.join(dir, '.memoc', 'wiki'), sourcePath), title, 'needs synthesis');
2286
2812
  addWikiListItem(path.join(dir, '.memoc', 'wiki', 'sources', 'README.md'), 'Source Records', path.basename(sourcePath), title, 'source record');
2287
2813
  addWikiListItem(path.join(dir, '.memoc', 'wiki', 'index.md'), 'Pages', pathRelativeMarkdown(path.join(dir, '.memoc', 'wiki'), sourcePath), title, 'source record');
2288
- appendMemocLog(dir, `ingest | Added source record ${normRel(dir, sourcePath)} from ${isUrl ? target : normRel(dir, path.resolve(dir, target))}.`);
2289
-
2290
2814
  console.log('\n memoc ingest\n');
2291
2815
  console.log(` Source record ${normRel(dir, sourcePath)}`);
2292
2816
  console.log(` Raw reference ${rawDisplay}`);
@@ -2315,8 +2839,6 @@ function runNote(dir) {
2315
2839
  ensureMemocFrontmatter(topicPath, dir);
2316
2840
  addWikiListItem(path.join(dir, '.memoc', 'wiki', 'topics', 'README.md'), 'Topic Pages', path.basename(topicPath), title, 'topic note');
2317
2841
  addWikiListItem(path.join(dir, '.memoc', 'wiki', 'index.md'), 'Saved Queries', pathRelativeMarkdown(path.join(dir, '.memoc', 'wiki'), topicPath), title, 'saved query/topic note');
2318
- appendMemocLog(dir, `note | Saved wiki topic ${normRel(dir, topicPath)}.`);
2319
-
2320
2842
  console.log('\n memoc note\n');
2321
2843
  console.log(` Topic ${normRel(dir, topicPath)}`);
2322
2844
  console.log(' Next Link related sources/topics, then run memoc lint-wiki.');
@@ -2334,7 +2856,9 @@ function ensureMemocBase(dir) {
2334
2856
  [path.join(memDir, 'raw/README.md'), tplRawReadme],
2335
2857
  [path.join(memDir, 'raw/files/README.md'), tplRawFilesReadme],
2336
2858
  [path.join(memDir, 'raw/urls/README.md'), tplRawUrlsReadme],
2337
- [path.join(memDir, 'log.md'), tplLog],
2859
+ [path.join(memDir, 'activity.md'), tplActivity],
2860
+ [path.join(memDir, 'actors/README.md'), tplActorsReadme],
2861
+ [path.join(memDir, 'worklog/README.md'), tplWorklogReadme],
2338
2862
  [path.join(memDir, '00-agent-index.md'), () => tplAgentIndex(p)],
2339
2863
  ];
2340
2864
  for (const [fp, tpl] of files) ensure(fp, tpl());
@@ -2488,12 +3012,6 @@ function addWikiListItem(filePath, heading, link, title, note) {
2488
3012
  write(filePath, src.replace(re, `$1${replacementBody}`));
2489
3013
  }
2490
3014
 
2491
- function appendMemocLog(dir, text) {
2492
- const fp = path.join(dir, '.memoc', 'log.md');
2493
- ensure(fp, tplLog());
2494
- fs.appendFileSync(fp, `\n## [${nowISO()}] ${text}\n`, 'utf8');
2495
- }
2496
-
2497
3015
  // ═══════════════════════════════════════════════════════════════════
2498
3016
  // SEARCH
2499
3017
  // ═══════════════════════════════════════════════════════════════════
@@ -2703,7 +3221,7 @@ function searchPriority(file, scope = 'memory') {
2703
3221
  '.memoc/04-handoff.md',
2704
3222
  '.memoc/06-project-rules.md',
2705
3223
  '.memoc/03-decisions.md',
2706
- '.memoc/log.md',
3224
+ '.memoc/activity.md',
2707
3225
  'AGENTS.md',
2708
3226
  'CLAUDE.md',
2709
3227
  'llms.txt',
@@ -2737,7 +3255,7 @@ function runTokens(dir) {
2737
3255
  ['03-decisions.md', path.join(memDir, '03-decisions.md')],
2738
3256
  ['04-handoff.md', path.join(memDir, '04-handoff.md')],
2739
3257
  ['06-project-rules.md', path.join(memDir, '06-project-rules.md')],
2740
- ['log.md', path.join(memDir, 'log.md')],
3258
+ ['activity.md', path.join(memDir, 'activity.md')],
2741
3259
  ];
2742
3260
 
2743
3261
  console.log('\n memoc tokens\n');
@@ -2770,13 +3288,62 @@ function runTokens(dir) {
2770
3288
  const summaryContent = read(path.join(memDir, 'session-summary.md'));
2771
3289
  const summaryBytes = Buffer.byteLength(summaryContent, 'utf8');
2772
3290
  if (summaryBytes > 800) {
2773
- console.log(`\n ⚠ session-summary.md is ${summaryBytes}B — recommended <800B. Run \`memoc trim-summary\`, then move completed history to log.md and resume details to 04-handoff.md.`);
3291
+ 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.`);
3292
+ }
3293
+ console.log();
3294
+ }
3295
+
3296
+ // ═══════════════════════════════════════════════════════════════════
3297
+ // DOCTOR — quick health checks for shared memoc repos
3298
+ // ═══════════════════════════════════════════════════════════════════
3299
+
3300
+ function runDoctor(dir) {
3301
+ const issues = [];
3302
+ const warnings = [];
3303
+ const memDir = path.join(dir, '.memoc');
3304
+ const summaryPath = path.join(memDir, 'session-summary.md');
3305
+ const summary = safeRead(summaryPath);
3306
+ if (!summary) issues.push('Missing .memoc/session-summary.md');
3307
+ else if (Buffer.byteLength(summary, 'utf8') > 800) warnings.push('session-summary.md exceeds 800B; run memoc trim-summary');
3308
+
3309
+ for (const fp of [
3310
+ path.join(dir, '.memoc', 'bin', 'memoc'),
3311
+ path.join(dir, '.memoc', 'bin', 'memoc.cmd'),
3312
+ path.join(dir, '.memoc', 'bin', 'memoc.ps1'),
3313
+ ]) {
3314
+ const src = safeRead(fp);
3315
+ if (src && (/C:\\Users\\|\/Users\/[^/]+\/\.local\/share\/memoc\/runtime/.test(src))) {
3316
+ issues.push(`${normRel(dir, fp)} contains a user-specific runtime path; run memoc upgrade`);
3317
+ }
3318
+ }
3319
+
3320
+ for (const fp of collectMemocMarkdownFiles(dir)) {
3321
+ const src = safeRead(fp);
3322
+ const fenceCount = (src.match(/^---$/gm) || []).length;
3323
+ if (fenceCount > 2) warnings.push(`${normRel(dir, fp)} may have nested frontmatter; run memoc upgrade`);
3324
+ }
3325
+
3326
+ const actor = detectActor(dir);
3327
+ if (actor.actor === 'unknown') warnings.push('Actor could not be detected; run memoc actor set <name>');
3328
+
3329
+ console.log('\n memoc doctor\n');
3330
+ console.log(` Actor ${actor.actor} (${actor.source})`);
3331
+ console.log(` Issues ${issues.length}`);
3332
+ console.log(` Warnings ${warnings.length}`);
3333
+ if (issues.length) {
3334
+ console.log('\n Issues:');
3335
+ for (const issue of issues) console.log(` - ${issue}`);
3336
+ }
3337
+ if (warnings.length) {
3338
+ console.log('\n Warnings:');
3339
+ for (const warning of warnings.slice(0, 20)) console.log(` - ${warning}`);
2774
3340
  }
3341
+ if (!issues.length && !warnings.length) console.log('\n Looks good.');
2775
3342
  console.log();
2776
3343
  }
2777
3344
 
2778
3345
  // ═══════════════════════════════════════════════════════════════════
2779
- // COMPRESS — archive old log.md entries to keep file small
3346
+ // COMPRESS — legacy log.md archiver
2780
3347
  // ═══════════════════════════════════════════════════════════════════
2781
3348
 
2782
3349
  function runCompress(dir) {
@@ -2812,6 +3379,7 @@ function runCompress(dir) {
2812
3379
  write(logPath, header.trimEnd() + '\n' + toKeep.join('') + '\n');
2813
3380
 
2814
3381
  console.log(`\n memoc compress\n`);
3382
+ console.log(' Legacy command: new activity should use .memoc/worklog/ instead of log.md.');
2815
3383
  console.log(` Archived ${toArchive.length} entries → .memoc/log-archive.md`);
2816
3384
  console.log(` Kept ${toKeep.length} recent entries in log.md`);
2817
3385
  const saved = Buffer.byteLength(toArchive.join(''), 'utf8');
@@ -2851,12 +3419,11 @@ function runTrimSummary(dir) {
2851
3419
  : '# Session Summary Archive\n\nOlder oversized startup summaries moved by `memoc trim-summary`.\n';
2852
3420
  fs.appendFileSync(archivePath, `${archiveHeader}\n## [${nowISO()}] archived summary (${beforeBytes}B)\n\n${src.trimEnd()}\n`, 'utf8');
2853
3421
  write(summaryPath, compact);
2854
- appendMemocLog(dir, `trim-summary | Archived oversized session summary (${beforeBytes}B → ${afterBytes}B).`);
2855
3422
 
2856
3423
  console.log('\n memoc trim-summary\n');
2857
3424
  console.log(` Archived .memoc/session-summary-archive.md`);
2858
3425
  console.log(` Rewrote .memoc/session-summary.md (${beforeBytes}B → ${afterBytes}B)`);
2859
- console.log(' Reminder Completed history belongs in log.md; resume details belong in 04-handoff.md.');
3426
+ console.log(' Reminder Completed history belongs in worklog; resume details belong in 04-handoff.md.');
2860
3427
  console.log('\n Done.\n');
2861
3428
  }
2862
3429
 
@@ -2866,7 +3433,7 @@ function compactSessionSummary(src) {
2866
3433
  '# Session Summary',
2867
3434
  `Last: ${nowISO()}`,
2868
3435
  'Replace this file instead of appending to it. Keep total size <800B and each section ≤3 bullets.',
2869
- 'Completed history belongs in `log.md`; incomplete/risky resume detail belongs in `04-handoff.md`.',
3436
+ 'Completed history belongs in actor worklogs; incomplete/risky resume detail belongs in `04-handoff.md`.',
2870
3437
  '',
2871
3438
  ];
2872
3439
 
@@ -2980,8 +3547,12 @@ if (!cmd || cmd === '--help' || cmd === '-h' || cmd === 'help') {
2980
3547
  console.log(' summary Print a tiny status/resume overview');
2981
3548
  console.log(' tokens Estimate token cost of current memory files');
2982
3549
  console.log(' trim-summary Archive and compact oversized session-summary.md');
2983
- console.log(' compress Archive old log.md entries to keep file small');
3550
+ console.log(' compress Legacy: archive old log.md entries');
2984
3551
  console.log(' add <agent> Add entry file for a specific agent (run without args to list)');
3552
+ console.log(' actor [set <name>] Show or set the local memoc actor');
3553
+ console.log(' work "<title>" Create a conflict-light actor worklog entry');
3554
+ console.log(' activity List recent memoc worklog entries');
3555
+ console.log(' doctor Check common memoc health issues');
2985
3556
  console.log(' search "<query>" Search memory/agent docs (use --snippets for line matches)');
2986
3557
  console.log(' grep "<query>" Search project source/text files (use --snippets for line matches)');
2987
3558
  console.log(' ingest <path|url> Create a raw/source record scaffold for wiki synthesis');
@@ -3005,6 +3576,10 @@ if (cmd === 'tokens') { runTokens(cwd); process.exit(0); }
3005
3576
  if (cmd === 'trim-summary') { runTrimSummary(cwd); process.exit(0); }
3006
3577
  if (cmd === 'compress') { runCompress(cwd); process.exit(0); }
3007
3578
  if (cmd === 'add') { runAdd(cwd); process.exit(0); }
3579
+ if (cmd === 'actor') { runActor(cwd); process.exit(0); }
3580
+ if (cmd === 'work') { runWork(cwd); process.exit(0); }
3581
+ if (cmd === 'activity') { runActivity(cwd); process.exit(0); }
3582
+ if (cmd === 'doctor') { runDoctor(cwd); process.exit(0); }
3008
3583
  if (cmd === 'search') { runSearch(cwd, 'memory'); process.exit(0); }
3009
3584
  if (cmd === 'grep') { runSearch(cwd, 'project'); process.exit(0); }
3010
3585
  if (cmd === 'ingest') { runIngest(cwd); process.exit(0); }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@kevin0181/memoc",
3
- "version": "1.1.4",
3
+ "version": "1.1.11",
4
4
  "description": "Give AI agents a memory. Scaffolds session-to-session context for Claude Code, Codex, Cursor, and more.",
5
5
  "keywords": [
6
6
  "ai",