@kevin0181/memoc 1.1.4 → 1.1.10

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 +39 -5
  2. package/bin/cli.js +592 -83
  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,7 +140,7 @@ 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
 
@@ -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. Existing projects can delete `.memoc/log.md` after preserving any useful history in worklogs or archives.
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
@@ -233,16 +233,121 @@ function markdownTitle(src, fallback) {
233
233
  return m ? m[1].trim() : fallback;
234
234
  }
235
235
 
236
- function tplMemocCmdWrapper(cliPath = runtimeCliPath()) {
237
- return `@echo off\r\nnode "${escapeCmdPath(cliPath)}" %*\r\n`;
236
+ function execGitConfig(dir, key) {
237
+ try {
238
+ return require('child_process')
239
+ .execFileSync('git', ['config', '--get', key], { cwd: dir, encoding: 'utf8', stdio: ['ignore', 'pipe', 'ignore'] })
240
+ .trim();
241
+ } catch {
242
+ return '';
243
+ }
244
+ }
245
+
246
+ function actorFile(dir) {
247
+ return path.join(dir, '.memoc', 'local', 'actor');
248
+ }
249
+
250
+ function sanitizeActor(value) {
251
+ return slugify(String(value || '').replace(/@.*$/, ''), 'unknown');
252
+ }
253
+
254
+ function detectActor(dir) {
255
+ if (process.env.MEMOC_ACTOR) return { actor: sanitizeActor(process.env.MEMOC_ACTOR), source: 'MEMOC_ACTOR' };
256
+ const localPath = actorFile(dir);
257
+ try {
258
+ const local = fs.readFileSync(localPath, 'utf8').trim();
259
+ if (local) return { actor: sanitizeActor(local), source: '.memoc/local/actor' };
260
+ } catch {}
261
+ const gitUser = execGitConfig(dir, 'user.name');
262
+ if (gitUser) return { actor: sanitizeActor(gitUser), source: 'git config user.name' };
263
+ const gitEmail = execGitConfig(dir, 'user.email');
264
+ if (gitEmail) return { actor: sanitizeActor(gitEmail), source: 'git config user.email' };
265
+ const osUser = process.env.USER || process.env.USERNAME || process.env.LOGNAME;
266
+ if (osUser) return { actor: sanitizeActor(osUser), source: 'OS user' };
267
+ return { actor: 'unknown', source: 'fallback' };
268
+ }
269
+
270
+ function gitBranch(dir) {
271
+ try {
272
+ return require('child_process')
273
+ .execFileSync('git', ['branch', '--show-current'], { cwd: dir, encoding: 'utf8', stdio: ['ignore', 'pipe', 'ignore'] })
274
+ .trim() || 'unknown';
275
+ } catch {
276
+ return 'unknown';
277
+ }
278
+ }
279
+
280
+ function gitStatusFiles(dir) {
281
+ try {
282
+ const out = require('child_process')
283
+ .execFileSync('git', ['status', '--short'], { cwd: dir, encoding: 'utf8', stdio: ['ignore', 'pipe', 'ignore'] })
284
+ .trim();
285
+ if (!out) return [];
286
+ return out.split(/\r?\n/)
287
+ .map(line => line.slice(3).trim())
288
+ .filter(Boolean)
289
+ .filter(file => !file.startsWith('.memoc/worklog/') && !file.startsWith('.memoc/activity.md'))
290
+ .slice(0, 12);
291
+ } catch {
292
+ return [];
293
+ }
294
+ }
295
+
296
+ function tplMemocCmdWrapper() {
297
+ return [
298
+ '@echo off',
299
+ 'set "MEMOC_RUNTIME=%MEMOC_RUNTIME_DIR%"',
300
+ 'if "%MEMOC_RUNTIME%"=="" (',
301
+ ' if not "%LOCALAPPDATA%"=="" (',
302
+ ' set "MEMOC_RUNTIME=%LOCALAPPDATA%\\memoc\\runtime"',
303
+ ' ) else (',
304
+ ' set "MEMOC_RUNTIME=%USERPROFILE%\\AppData\\Local\\memoc\\runtime"',
305
+ ' )',
306
+ ')',
307
+ 'set "MEMOC_CLI=%MEMOC_RUNTIME%\\bin\\cli.js"',
308
+ 'if exist "%MEMOC_CLI%" (',
309
+ ' node "%MEMOC_CLI%" %*',
310
+ ') else (',
311
+ ' npx @kevin0181/memoc@latest %*',
312
+ ')',
313
+ 'exit /b %ERRORLEVEL%',
314
+ '',
315
+ ].join('\r\n');
238
316
  }
239
317
 
240
- function tplMemocPs1Wrapper(cliPath = runtimeCliPath()) {
241
- return `& node ${psSingleQuote(cliPath)} @args\nexit $LASTEXITCODE\n`;
318
+ function tplMemocPs1Wrapper() {
319
+ return [
320
+ '$runtime = $env:MEMOC_RUNTIME_DIR',
321
+ 'if (-not $runtime) {',
322
+ ' if ($env:LOCALAPPDATA) { $runtime = Join-Path $env:LOCALAPPDATA "memoc\\runtime" }',
323
+ ' else { $runtime = Join-Path $env:USERPROFILE "AppData\\Local\\memoc\\runtime" }',
324
+ '}',
325
+ '$cli = Join-Path $runtime "bin\\cli.js"',
326
+ 'if (Test-Path $cli) {',
327
+ ' & node $cli @args',
328
+ '} else {',
329
+ ' & npx @kevin0181/memoc@latest @args',
330
+ '}',
331
+ 'exit $LASTEXITCODE',
332
+ '',
333
+ ].join('\n');
242
334
  }
243
335
 
244
- function tplMemocShWrapper(cliPath = runtimeCliPath()) {
245
- return `#!/bin/sh\nexec node ${shellSingleQuote(cliPath)} "$@"\n`;
336
+ function tplMemocShWrapper() {
337
+ return [
338
+ '#!/bin/sh',
339
+ 'if [ -n "$MEMOC_RUNTIME_DIR" ]; then',
340
+ ' memoc_runtime="$MEMOC_RUNTIME_DIR"',
341
+ 'else',
342
+ ' memoc_runtime="${HOME:-$PWD}/.local/share/memoc/runtime"',
343
+ 'fi',
344
+ 'memoc_cli="$memoc_runtime/bin/cli.js"',
345
+ 'if [ -f "$memoc_cli" ]; then',
346
+ ' exec node "$memoc_cli" "$@"',
347
+ 'fi',
348
+ 'exec npx @kevin0181/memoc@latest "$@"',
349
+ '',
350
+ ].join('\n');
246
351
  }
247
352
 
248
353
  function defaultUserBinDir() {
@@ -274,11 +379,11 @@ function tplEnvSh() {
274
379
  }
275
380
 
276
381
  function ensurePathHelpers(dir, mark) {
277
- const cliPath = ensureRuntimeInstall(mark);
382
+ ensureRuntimeInstall(mark);
278
383
  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],
384
+ [path.join(dir, '.memoc', 'bin', 'memoc.cmd'), tplMemocCmdWrapper, false],
385
+ [path.join(dir, '.memoc', 'bin', 'memoc.ps1'), tplMemocPs1Wrapper, false],
386
+ [path.join(dir, '.memoc', 'bin', 'memoc'), tplMemocShWrapper, true],
282
387
  [path.join(dir, '.memoc', 'env.ps1'), tplEnvPs1, false],
283
388
  [path.join(dir, '.memoc', 'env.sh'), tplEnvSh, true],
284
389
  ];
@@ -293,15 +398,16 @@ function ensurePathHelpers(dir, mark) {
293
398
 
294
399
  function ensureUserLauncher(mark) {
295
400
  const userBin = defaultUserBinDir();
296
- writeLaunchers(userBin, mark, 'user bin', ensureRuntimeInstall(mark));
401
+ ensureRuntimeInstall(mark);
402
+ writeLaunchers(userBin, mark, 'user bin');
297
403
  return userBin;
298
404
  }
299
405
 
300
- function writeLaunchers(binDir, mark, label, cliPath = ensureRuntimeInstall(mark)) {
406
+ function writeLaunchers(binDir, mark, label) {
301
407
  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],
408
+ [path.join(binDir, 'memoc.cmd'), tplMemocCmdWrapper, false],
409
+ [path.join(binDir, 'memoc.ps1'), tplMemocPs1Wrapper, false],
410
+ [path.join(binDir, 'memoc'), tplMemocShWrapper, true],
305
411
  ];
306
412
 
307
413
  for (const [fp, tpl, executable] of files) {
@@ -398,7 +504,8 @@ function ensureCurrentPathLauncher(mark) {
398
504
  mark('skip', 'active PATH launcher (no writable PATH directory found)');
399
505
  return false;
400
506
  }
401
- writeLaunchers(target, mark, 'active PATH', ensureRuntimeInstall(mark));
507
+ ensureRuntimeInstall(mark);
508
+ writeLaunchers(target, mark, 'active PATH');
402
509
  return true;
403
510
  }
404
511
 
@@ -631,6 +738,7 @@ function managedBlock() {
631
738
  - [ ] If memory search is not enough, search project files with \`memoc grep "<query>" --limit 5\` (or wrapper fallback)
632
739
  - [ ] If asked to refresh/update memoc project memory, run \`memoc update\` first; this refreshes managed sections, wiki links, and Obsidian tags.
633
740
  - [ ] 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\`.
741
+ - [ ] 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
742
  - [ ] Keep output small: \`summary\`, \`search --limit\`, \`grep --limit\`, \`--snippets\`
635
743
 
636
744
  ## Before Finishing _(update only applicable files; skip Q&A / throwaway exploration)_
@@ -640,7 +748,8 @@ function managedBlock() {
640
748
  - [ ] Rule/preference set? Update \`06-project-rules.md\`
641
749
  - [ ] Wiki/systems work? Read \`skills/project-memory-maintainer/SKILL.md\`
642
750
  - [ ] 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\`.
751
+ - [ ] Shared repo work? Prefer \`memoc work "<title>" --from-git\` over appending shared files; run \`memoc activity --write\` only when regenerating indexes.
752
+ - [ ] 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
753
  ${MGMT_E}`;
645
754
  }
646
755
 
@@ -675,8 +784,8 @@ function coreLlmsInner() {
675
784
  - [Project Brief](.memoc/00-project-brief.md): short identity and direction.
676
785
  - [Workflow](.memoc/01-agent-workflow.md): update trigger matrix.
677
786
  - [Decisions](.memoc/03-decisions.md): durable decisions.
678
- - [Log](.memoc/log.md): append-only history.
679
787
  - [Systems](.memoc/systems/README.md): subsystem docs.
788
+ - [Activity](.memoc/activity.md): generated worklog index.
680
789
  - [Raw Sources](.memoc/raw/README.md): immutable source material; do not read by default.
681
790
  - [Wiki](.memoc/wiki/index.md): synthesized knowledge.`;
682
791
  }
@@ -848,6 +957,12 @@ function obsidianFrontmatterSpec(relPath) {
848
957
  } else if (rel.startsWith('.memoc/raw/')) {
849
958
  type = 'raw';
850
959
  tags.push('memoc/raw');
960
+ } else if (rel.startsWith('.memoc/worklog/')) {
961
+ type = 'worklog';
962
+ tags.push('memoc/worklog');
963
+ } else if (rel.startsWith('.memoc/actors/')) {
964
+ type = 'actor';
965
+ tags.push('memoc/actor');
851
966
  } else if (rel.startsWith('skills/project-memory-maintainer/')) {
852
967
  type = 'skill';
853
968
  tags.push('memoc/skill');
@@ -864,10 +979,18 @@ function obsidianFrontmatterSpec(relPath) {
864
979
  function mergeYamlFrontmatter(src, spec) {
865
980
  const fm = parseYamlFrontmatter(src);
866
981
  if (!fm) {
867
- return `${formatMemocFrontmatter(spec)}\n${src}`;
982
+ return `${formatMemocFrontmatter(spec)}\n${String(src || '').replace(/^\uFEFF/, '')}`;
868
983
  }
869
984
 
870
- const lines = fm.body.split(/\r?\n/);
985
+ let body = fm.body;
986
+ let rest = String(src || '').slice(fm.end).replace(/^\uFEFF/, '');
987
+ const nested = parseYamlFrontmatter(rest);
988
+ if (nested) {
989
+ body = `${nested.body}\n${body}`;
990
+ rest = rest.slice(nested.end).replace(/^\uFEFF/, '');
991
+ }
992
+
993
+ const lines = body.split(/\r?\n/);
871
994
  const existingTags = readYamlTags(lines);
872
995
  const mergedTags = [...new Set([...existingTags, ...spec.tags])];
873
996
  const nextLines = mergeYamlScalar(lines, 'memoc', 'true');
@@ -880,14 +1003,16 @@ function mergeYamlFrontmatter(src, spec) {
880
1003
  mergeYamlTags(nextLines, mergedTags);
881
1004
 
882
1005
  const nextFm = ['---', ...nextLines, '---'].join('\n');
883
- return nextFm + src.slice(fm.end);
1006
+ return `${nextFm}\n${rest.replace(/^\r?\n/, '')}`;
884
1007
  }
885
1008
 
886
1009
  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?/);
1010
+ const text = String(src || '').replace(/^\uFEFF/, '');
1011
+ const offset = text.length === src.length ? 0 : 1;
1012
+ if (!text.startsWith('---\n') && !text.startsWith('---\r\n')) return null;
1013
+ const m = text.match(/^---\r?\n([\s\S]*?)\r?\n---\r?\n?/);
889
1014
  if (!m) return null;
890
- return { body: m[1], end: m[0].length };
1015
+ return { body: m[1], end: m[0].length + offset };
891
1016
  }
892
1017
 
893
1018
  function formatMemocFrontmatter(spec) {
@@ -1097,7 +1222,9 @@ ${SNAP_E}
1097
1222
  - [Done Checklist](05-done-checklist.md)
1098
1223
  - [Project Rules](06-project-rules.md)
1099
1224
  - [Session Summary](session-summary.md)
1100
- - [Project Log](log.md)
1225
+ - [Activity](activity.md)
1226
+ - [Actors](actors/README.md)
1227
+ - [Worklog](worklog/README.md)
1101
1228
  - [Wiki Index](wiki/index.md)
1102
1229
  - [Raw Sources](raw/README.md)
1103
1230
  - [Systems Index](systems/README.md)
@@ -1137,7 +1264,7 @@ _None yet._
1137
1264
 
1138
1265
  ## Completed Tasks
1139
1266
 
1140
- See \`.memoc/log.md\` for full history.
1267
+ See \`.memoc/worklog/\` for full shared activity history.
1141
1268
 
1142
1269
  ## Commands
1143
1270
 
@@ -1149,7 +1276,7 @@ _None yet._
1149
1276
 
1150
1277
  ## Change Log
1151
1278
 
1152
- See \`.memoc/log.md\`.
1279
+ See \`.memoc/worklog/\` and generated \`.memoc/activity.md\`.
1153
1280
  `;
1154
1281
  }
1155
1282
 
@@ -1157,7 +1284,7 @@ function tplSessionSummary() {
1157
1284
  return `# Session Summary
1158
1285
  Last: ${nowISO()}
1159
1286
  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\`.
1287
+ Completed history belongs in actor worklogs; incomplete/risky resume detail belongs in \`04-handoff.md\`.
1161
1288
  Agent-owned — updated by you, not by \`memoc update\`.
1162
1289
 
1163
1290
  ## Status
@@ -1194,7 +1321,6 @@ On-demand reference only. The entry-file managed block is authoritative.
1194
1321
  | \`.memoc/01-agent-workflow.md\` | When update routing is unclear |
1195
1322
  | \`.memoc/05-done-checklist.md\` | Before finishing substantial work |
1196
1323
  | \`.memoc/03-decisions.md\` | When a durable decision was made |
1197
- | \`.memoc/log.md\` | For append-only history |
1198
1324
  | \`.memoc/memoc-usage.md\` | For command details |
1199
1325
  | \`.memoc/systems/*.md\` | Before touching a specific subsystem |
1200
1326
  | \`.memoc/wiki/*.md\` | For synthesized project knowledge |
@@ -1227,15 +1353,16 @@ Shared protocol for any coding agent.
1227
1353
  | Trigger | Update |
1228
1354
  | --- | --- |
1229
1355
  | 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\` |
1356
+ | User creates or changes a requirement | \`02-current-project-state.md\`, \`06-project-rules.md\`, \`memoc work "<title>" --from-git\` |
1357
+ | Code, config, data, or assets changed | \`02-current-project-state.md\`, relevant \`systems/*.md\`, \`memoc work "<title>" --from-git\` |
1232
1358
  | Architecture or system behavior changed | relevant \`systems/*.md\`, \`03-decisions.md\` |
1233
1359
  | 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\` |
1360
+ | Work is substantial enough to resume later | \`04-handoff.md\`, \`02-current-project-state.md\`, \`memoc work "<title>" --from-git\` |
1235
1361
  | Durable knowledge was learned | \`wiki/*.md\`, \`wiki/index.md\` |
1236
1362
  | Source material should feed the wiki | \`memoc ingest <path-or-url>\`, then synthesize affected \`wiki/topics/*.md\` |
1237
1363
  | 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\` |
1364
+ | Shared repo work should be traceable | \`memoc work "<title>"\`; avoid appending long details to shared core files |
1365
+ | \`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
1366
 
1240
1367
  ## Usually No Update Needed
1241
1368
 
@@ -1250,7 +1377,7 @@ Shared protocol for any coding agent.
1250
1377
  - \`02-current-project-state.md\`: current status, tasks, commands, recent notes.
1251
1378
  - \`04-handoff.md\`: resume context, blockers, verified/unverified checks.
1252
1379
  - \`03-decisions.md\`: append durable decisions only.
1253
- - \`log.md\`: full history; keep bulky completed work here.
1380
+ - \`worklog/<actor>/YYYY-MM/*.md\`: actor-scoped append-by-new-file activity records for shared repos.
1254
1381
  - \`systems/*.md\` and \`wiki/*.md\`: on-demand durable knowledge.
1255
1382
  `;
1256
1383
  }
@@ -1322,7 +1449,7 @@ Run through this before saying substantial work is complete.
1322
1449
  - [ ] \`.memoc/02-current-project-state.md\` reflects the new status.
1323
1450
  - [ ] \`.memoc/03-decisions.md\` updated if a durable decision was made.
1324
1451
  - [ ] \`.memoc/04-handoff.md\` updated if work is incomplete or risky.
1325
- - [ ] \`.memoc/log.md\` has a new entry for meaningful work.
1452
+ - [ ] Meaningful shared work has a \`.memoc/worklog/<actor>/YYYY-MM/*.md\` entry.
1326
1453
  - [ ] Relevant \`.memoc/systems/*.md\` or wiki pages updated.
1327
1454
 
1328
1455
  ## Communication
@@ -1347,7 +1474,7 @@ Durable user and project preferences live here. Update when the user gives a rul
1347
1474
  ## Agent Behavior Preferences
1348
1475
 
1349
1476
  - Be factual and operational in memory docs.
1350
- - Keep logs concise; do not paste temporary command output unless it changes future work.
1477
+ - Keep memory notes concise; do not paste temporary command output unless it changes future work.
1351
1478
  - Preserve user changes and avoid reverting unrelated work.
1352
1479
  - State unverified parts honestly in the final answer and handoff.
1353
1480
 
@@ -1360,9 +1487,75 @@ _None yet._
1360
1487
  function tplLog() {
1361
1488
  return `# Project Log
1362
1489
 
1363
- Append-only chronological log for project memory updates.
1490
+ Legacy file. New memoc activity belongs in actor worklogs under \`.memoc/worklog/\`.
1491
+
1492
+ This file is no longer created for new projects. Existing projects may delete it after migrating useful entries to worklogs.
1493
+ `;
1494
+ }
1495
+
1496
+ function tplActivity() {
1497
+ return `# Activity
1498
+
1499
+ Shared activity index for memoc work logs.
1500
+
1501
+ ## How To Use
1502
+
1503
+ - Use \`memoc work "<title>"\` after meaningful work so shared repos get conflict-light per-actor records.
1504
+ - Keep this file short; detailed work belongs in [worklog](worklog/README.md).
1505
+ - Actor is detected from \`MEMOC_ACTOR\`, \`.memoc/local/actor\`, git config, git email, or OS user.
1506
+
1507
+ ## Recent Work
1508
+
1509
+ _None yet._
1510
+
1511
+ ## Related
1512
+
1513
+ - [Actors](actors/README.md)
1514
+ - [Worklog](worklog/README.md)
1515
+ `;
1516
+ }
1517
+
1518
+ function tplActorsReadme() {
1519
+ return `# Actors
1364
1520
 
1365
- ## [${nowISO()}] init | Initialized memoc memory structure.
1521
+ People or agents that update memoc in this shared repo.
1522
+
1523
+ ## Actor Detection
1524
+
1525
+ 1. \`MEMOC_ACTOR\`
1526
+ 2. \`.memoc/local/actor\` set by \`memoc actor set <name>\`
1527
+ 3. \`git config user.name\`
1528
+ 4. \`git config user.email\`
1529
+ 5. OS username
1530
+
1531
+ \`.memoc/local/\` is ignored by git so each machine can keep its own actor setting.
1532
+
1533
+ ## Actors
1534
+
1535
+ _None yet. Use \`memoc actor set <name>\` or \`memoc work "<title>"\`._
1536
+ `;
1537
+ }
1538
+
1539
+ function tplWorklogReadme() {
1540
+ return `# Worklog
1541
+
1542
+ Conflict-light per-actor work records.
1543
+
1544
+ ## Rules
1545
+
1546
+ - Prefer creating new worklog files over appending shared core memory files.
1547
+ - Keep \`session-summary.md\` as the tiny current snapshot.
1548
+ - Put detailed completed work here; put only team-critical unfinished risk in \`04-handoff.md\`.
1549
+
1550
+ ## Layout
1551
+
1552
+ \`\`\`text
1553
+ worklog/<actor>/YYYY-MM/YYYYMMDDTHHMM-title.md
1554
+ \`\`\`
1555
+
1556
+ ## Recent Work
1557
+
1558
+ _None yet._
1366
1559
  `;
1367
1560
  }
1368
1561
 
@@ -1388,6 +1581,14 @@ memoc upgrade
1388
1581
  memoc update
1389
1582
  memoc trim-summary
1390
1583
 
1584
+ # Shared repo actor/work tracking
1585
+ memoc actor
1586
+ memoc actor set neneee
1587
+ memoc work "Auth refresh fix" --from-git
1588
+ memoc activity
1589
+ memoc activity --write
1590
+ memoc doctor
1591
+
1391
1592
  # Tiny status overview
1392
1593
  memoc summary
1393
1594
 
@@ -1420,6 +1621,12 @@ Use \`memoc search\` for known concepts, changed areas, decisions, tasks, or han
1420
1621
 
1421
1622
  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
1623
 
1624
+ ## Shared Repo Activity
1625
+
1626
+ 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.
1627
+
1628
+ \`.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.
1629
+
1423
1630
  ## When To Run Memory Updates
1424
1631
 
1425
1632
  Use \`memoc update\` or \`skills/project-memory-maintainer/SKILL.md\` when:
@@ -1431,12 +1638,13 @@ Use \`memoc update\` or \`skills/project-memory-maintainer/SKILL.md\` when:
1431
1638
  - A decision was made that future agents should not revisit blindly.
1432
1639
  - Work is partial, multi-step, blocked, or likely to be resumed by another agent.
1433
1640
  - New durable knowledge belongs in \`.memoc/wiki/\` or a subsystem doc.
1641
+ - Shared work should be traceable without causing conflicts.
1434
1642
 
1435
1643
  Usually skip for pure Q&A, throwaway exploration, or tiny edits with no future impact.
1436
1644
 
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\`.
1645
+ 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
1646
 
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\`.
1647
+ \`.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
1648
 
1441
1649
  ## Updating The Wiki
1442
1650
 
@@ -1450,7 +1658,7 @@ Create a new Markdown file under \`.memoc/wiki/\` when synthesized knowledge sho
1450
1658
  After creating or editing wiki pages:
1451
1659
  1. Update \`.memoc/wiki/index.md\`.
1452
1660
  2. Run \`memoc lint-wiki\`.
1453
- 3. Append \`.memoc/log.md\`.
1661
+ 3. If the change is meaningful shared work, run \`memoc work "<title>" --from-git\`.
1454
1662
 
1455
1663
  Useful scaffolds:
1456
1664
 
@@ -1590,7 +1798,6 @@ _None yet. Use \`memoc note "<title>"\` for durable analysis or query results th
1590
1798
  - [Agent Index](../00-agent-index.md)
1591
1799
  - [Project Brief](../00-project-brief.md)
1592
1800
  - [Current Project State](../02-current-project-state.md)
1593
- - [Project Log](../log.md)
1594
1801
  `;
1595
1802
  }
1596
1803
 
@@ -1774,14 +1981,16 @@ Use this local skill after meaningful project work so future agents can continue
1774
1981
  - Update \`.memoc/04-handoff.md\` before ending substantial work.
1775
1982
  - Check \`.memoc/05-done-checklist.md\` before saying substantial work is complete.
1776
1983
  - Update \`.memoc/06-project-rules.md\` when the user gives durable preferences.
1777
- - Append \`.memoc/log.md\` for meaningful changes, decisions, and handoffs.
1984
+ - Create a short actor worklog with \`memoc work "<title>" --from-git\` for meaningful changes, decisions, and handoffs.
1778
1985
  - Create or update \`.memoc/systems/*.md\` when a subsystem needs durable explanation.
1779
1986
  - Create or update \`.memoc/wiki/*.md\` when synthesized knowledge should compound over time.
1780
1987
  - Use \`memoc ingest <path-or-url>\` for source material and \`memoc note "<title>"\` for durable query results or analysis.
1988
+ - 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
1989
  - 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
1990
  - 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\`.
1991
+ - Keep completed history in actor worklogs; keep current-state files short.
1992
+ - Move completed session details out of \`session-summary.md\` into \`.memoc/worklog/<actor>/YYYY-MM/\`; move incomplete/risky resume details into \`04-handoff.md\`.
1993
+ - 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
1994
  - Keep tool output small; prefer \`summary\`, file-only search, \`--limit\`, and targeted reads.
1786
1995
 
1787
1996
  ## Wiki Link Rules
@@ -1890,17 +2099,16 @@ function ensureClaudeStopHookFile(dir, mark) {
1890
2099
 
1891
2100
  function ensurePendingGitignore(dir, mark) {
1892
2101
  const gitignorePath = path.join(dir, '.gitignore');
1893
- const PENDING_ENTRY = '.memoc/.pending';
2102
+ const entries = ['.memoc/.pending', '.memoc/local/'];
1894
2103
  const gitignoreContent = fs.existsSync(gitignorePath)
1895
2104
  ? 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)');
2105
+ const lines = gitignoreContent.split(/\r?\n/).map(line => line.trim());
2106
+ const missing = entries.filter(entry => !lines.includes(entry));
2107
+ if (missing.length) {
2108
+ fs.appendFileSync(gitignorePath, (gitignoreContent.endsWith('\n') ? '' : '\n') + missing.join('\n') + '\n', 'utf8');
2109
+ mark('update', `.gitignore (${missing.join(', ')} added)`);
1902
2110
  } else {
1903
- mark('skip', '.gitignore (.memoc/.pending already present)');
2111
+ mark('skip', '.gitignore (memoc local entries already present)');
1904
2112
  }
1905
2113
  }
1906
2114
 
@@ -1975,7 +2183,9 @@ function run(dir, forceUpdate, action = 'update') {
1975
2183
  [path.join(memDir, '04-handoff.md'), tplHandoff],
1976
2184
  [path.join(memDir, '05-done-checklist.md'), tplDoneChecklist],
1977
2185
  [path.join(memDir, '06-project-rules.md'), tplProjectRules],
1978
- [path.join(memDir, 'log.md'), tplLog],
2186
+ [path.join(memDir, 'activity.md'), tplActivity],
2187
+ [path.join(memDir, 'actors/README.md'), tplActorsReadme],
2188
+ [path.join(memDir, 'worklog/README.md'), tplWorklogReadme],
1979
2189
  [path.join(memDir, 'memoc-usage.md'), tplMemocUsage],
1980
2190
  [path.join(memDir, 'systems/README.md'), tplSystemsReadme],
1981
2191
  [path.join(memDir, 'raw/README.md'), tplRawReadme],
@@ -2081,7 +2291,9 @@ function run(dir, forceUpdate, action = 'update') {
2081
2291
  [path.join(memDir, '04-handoff.md'), tplHandoff],
2082
2292
  [path.join(memDir, '05-done-checklist.md'), tplDoneChecklist],
2083
2293
  [path.join(memDir, '06-project-rules.md'), tplProjectRules],
2084
- [path.join(memDir, 'log.md'), tplLog],
2294
+ [path.join(memDir, 'activity.md'), tplActivity],
2295
+ [path.join(memDir, 'actors/README.md'), tplActorsReadme],
2296
+ [path.join(memDir, 'worklog/README.md'), tplWorklogReadme],
2085
2297
  [path.join(memDir, 'memoc-usage.md'), tplMemocUsage],
2086
2298
  [path.join(memDir, 'systems/README.md'), tplSystemsReadme],
2087
2299
  [path.join(memDir, 'raw/README.md'), tplRawReadme],
@@ -2115,15 +2327,7 @@ function run(dir, forceUpdate, action = 'update') {
2115
2327
  ensurePathHelpers(dir, mark);
2116
2328
  ensurePathRegistration(dir, mark);
2117
2329
 
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
- }
2330
+ mark('skip', '.memoc/log.md (legacy; shared history belongs in worklog)');
2127
2331
  }
2128
2332
 
2129
2333
  hideOnWindows(memDir);
@@ -2162,6 +2366,262 @@ function runAdd(dir) {
2162
2366
  console.log('\n Done.');
2163
2367
  }
2164
2368
 
2369
+ // ═══════════════════════════════════════════════════════════════════
2370
+ // ACTOR / WORKLOG — conflict-light shared repo activity tracking
2371
+ // ═══════════════════════════════════════════════════════════════════
2372
+
2373
+ function runActor(dir) {
2374
+ const sub = (process.argv[3] || '').toLowerCase();
2375
+ if (sub === 'set') {
2376
+ const name = process.argv.slice(4).join(' ').trim();
2377
+ if (!name) {
2378
+ console.error('\n Usage: memoc actor set <name>');
2379
+ process.exit(1);
2380
+ }
2381
+ const actor = sanitizeActor(name);
2382
+ write(actorFile(dir), `${actor}\n`);
2383
+ ensurePendingGitignore(dir, () => {});
2384
+ console.log('\n memoc actor\n');
2385
+ console.log(` Set local actor: ${actor}`);
2386
+ console.log(' Stored in .memoc/local/actor (ignored by git).');
2387
+ console.log();
2388
+ return;
2389
+ }
2390
+
2391
+ const detected = detectActor(dir);
2392
+ console.log('\n memoc actor\n');
2393
+ console.log(` Actor ${detected.actor}`);
2394
+ console.log(` Source ${detected.source}`);
2395
+ console.log('\n Use `memoc actor set <name>` to override locally.\n');
2396
+ }
2397
+
2398
+ function runWork(dir) {
2399
+ const rawArgs = process.argv.slice(3);
2400
+ const opts = { status: 'done', body: '', fromGit: true };
2401
+ const titleParts = [];
2402
+ for (let i = 0; i < rawArgs.length; i++) {
2403
+ const arg = rawArgs[i];
2404
+ if (arg === '--status') {
2405
+ opts.status = sanitizeActor(rawArgs[++i] || 'done');
2406
+ continue;
2407
+ }
2408
+ if (arg.startsWith('--status=')) {
2409
+ opts.status = sanitizeActor(arg.slice('--status='.length) || 'done');
2410
+ continue;
2411
+ }
2412
+ if (arg === '--body') {
2413
+ opts.body = rawArgs.slice(i + 1).join(' ');
2414
+ break;
2415
+ }
2416
+ if (arg === '--from-git') {
2417
+ opts.fromGit = true;
2418
+ continue;
2419
+ }
2420
+ if (arg === '--no-git') {
2421
+ opts.fromGit = false;
2422
+ continue;
2423
+ }
2424
+ titleParts.push(arg);
2425
+ }
2426
+
2427
+ const title = titleParts.join(' ').trim();
2428
+ if (!title) {
2429
+ console.error('\n Usage: memoc work "<title>" [--status done|wip|blocked] [--from-git|--no-git] [--body "summary"]');
2430
+ process.exit(1);
2431
+ }
2432
+
2433
+ ensureMemocBase(dir);
2434
+ const detected = detectActor(dir);
2435
+ ensureActorProfile(dir, detected);
2436
+ const stamp = new Date().toISOString().slice(0, 16).replace(/[-:]/g, '').replace('T', 'T');
2437
+ const month = todayISO().slice(0, 7);
2438
+ const fileName = `${stamp}-${slugify(title, 'work')}.md`;
2439
+ const workPath = uniquePath(path.join(dir, '.memoc', 'worklog', detected.actor, month, fileName));
2440
+ write(workPath, worklogRecord(dir, title, detected, opts));
2441
+ ensureMemocFrontmatter(workPath, dir);
2442
+
2443
+ console.log('\n memoc work\n');
2444
+ console.log(` Actor ${detected.actor} (${detected.source})`);
2445
+ console.log(` Work ${normRel(dir, workPath)}`);
2446
+ console.log(' Next Fill only Summary/Verification if needed; run `memoc activity --write` to regenerate indexes.');
2447
+ console.log();
2448
+ }
2449
+
2450
+ function runActivity(dir) {
2451
+ const writeIndex = process.argv.slice(3).includes('--write');
2452
+ const workRoot = path.join(dir, '.memoc', 'worklog');
2453
+ const files = listMarkdownFiles(workRoot)
2454
+ .filter(fp => path.basename(fp) !== 'README.md')
2455
+ .sort()
2456
+ .reverse();
2457
+ const recent = files.slice(0, 20);
2458
+
2459
+ if (writeIndex) {
2460
+ writeActivityIndexes(dir, recent);
2461
+ ensureObsidianFrontmatter(dir, () => {});
2462
+ }
2463
+
2464
+ console.log('\n memoc activity\n');
2465
+ if (!recent.length) {
2466
+ console.log(' No worklog entries yet. Use `memoc work "<title>"`.');
2467
+ console.log();
2468
+ return;
2469
+ }
2470
+ for (const fp of recent) {
2471
+ const src = safeRead(fp);
2472
+ const title = markdownTitle(src, path.basename(fp, '.md'));
2473
+ const actor = (src.match(/^actor:\s*(.+)$/m) || [])[1] || 'unknown';
2474
+ const status = (src.match(/^status:\s*(.+)$/m) || [])[1] || 'unknown';
2475
+ console.log(` - ${normRel(dir, fp)} ${actor} ${status} ${title}`);
2476
+ }
2477
+ if (writeIndex) console.log('\n Wrote .memoc/activity.md and .memoc/worklog/README.md');
2478
+ console.log();
2479
+ }
2480
+
2481
+ function ensureActorProfile(dir, detected) {
2482
+ const actorPath = path.join(dir, '.memoc', 'actors', `${detected.actor}.md`);
2483
+ if (fs.existsSync(actorPath)) return;
2484
+ write(actorPath, actorProfile(detected));
2485
+ ensureMemocFrontmatter(actorPath, dir);
2486
+ }
2487
+
2488
+ function actorProfile(detected) {
2489
+ return `# ${detected.actor}
2490
+
2491
+ ## Identity
2492
+
2493
+ - Actor: ${detected.actor}
2494
+ - First detected from: ${detected.source}
2495
+ - First seen: ${nowISO()}
2496
+
2497
+ ## Notes
2498
+
2499
+ _Add stable collaboration preferences or ownership notes only when useful._
2500
+
2501
+ ## Related
2502
+
2503
+ - [Actors](README.md)
2504
+ - [Activity](../activity.md)
2505
+ - [Worklog](../worklog/README.md)
2506
+ `;
2507
+ }
2508
+
2509
+ function worklogRecord(dir, title, detected, opts) {
2510
+ const branch = gitBranch(dir);
2511
+ const changedFiles = opts.fromGit ? gitStatusFiles(dir) : [];
2512
+ return `# ${title}
2513
+
2514
+ actor: ${detected.actor}
2515
+ actor_source: ${detected.source}
2516
+ branch: ${branch}
2517
+ status: ${opts.status}
2518
+ created: ${nowISO()}
2519
+
2520
+ ## Summary
2521
+
2522
+ ${opts.body ? `- ${opts.body}` : '_1-3 bullets only. Keep this as a short receipt, not a report._'}
2523
+
2524
+ ## Changed Files
2525
+
2526
+ ${changedFiles.length ? changedFiles.map(file => `- \`${file}\``).join('\n') : '_None detected. Use `memoc work "<title>" --from-git` after editing files to prefill this section._'}
2527
+
2528
+ ## Verification
2529
+
2530
+ _Commands run or checks not run. Keep to 1-3 bullets._
2531
+
2532
+ ## Follow-up
2533
+
2534
+ _None._
2535
+
2536
+ ## Related
2537
+
2538
+ - [Activity](../../../activity.md)
2539
+ - [Worklog](../../README.md)
2540
+ - [Actor](../../../actors/${detected.actor}.md)
2541
+ `;
2542
+ }
2543
+
2544
+ function writeActivityIndexes(dir, recentFiles) {
2545
+ const activityPath = path.join(dir, '.memoc', 'activity.md');
2546
+ const worklogReadme = path.join(dir, '.memoc', 'worklog', 'README.md');
2547
+ const actorsReadme = path.join(dir, '.memoc', 'actors', 'README.md');
2548
+ const rows = recentFiles.map(fp => {
2549
+ const src = safeRead(fp);
2550
+ const title = markdownTitle(src, path.basename(fp, '.md'));
2551
+ const actor = (src.match(/^actor:\s*(.+)$/m) || [])[1] || 'unknown';
2552
+ const status = (src.match(/^status:\s*(.+)$/m) || [])[1] || 'unknown';
2553
+ return { fp, title, actor, status };
2554
+ });
2555
+ const activityItems = rows.length
2556
+ ? rows.map(row => `- [${row.title}](${pathRelativeMarkdown(path.join(dir, '.memoc'), row.fp).replace(/^\.\//, '')}) — ${row.actor} ${row.status}.`).join('\n')
2557
+ : '_None yet._';
2558
+ write(activityPath, `# Activity
2559
+
2560
+ Generated shared activity index for memoc work logs.
2561
+
2562
+ Last generated: ${nowISO()}
2563
+
2564
+ ## Recent Work
2565
+
2566
+ ${activityItems}
2567
+
2568
+ ## Related
2569
+
2570
+ - [Actors](actors/README.md)
2571
+ - [Worklog](worklog/README.md)
2572
+ `);
2573
+
2574
+ const worklogItems = rows.length
2575
+ ? rows.map(row => `- [${row.title}](${pathRelativeMarkdown(path.join(dir, '.memoc', 'worklog'), row.fp).replace(/^\.\//, '')}) — ${row.actor} ${row.status}.`).join('\n')
2576
+ : '_None yet._';
2577
+ write(worklogReadme, `# Worklog
2578
+
2579
+ Generated index of conflict-light per-actor work records.
2580
+
2581
+ Last generated: ${nowISO()}
2582
+
2583
+ ## Layout
2584
+
2585
+ \`\`\`text
2586
+ worklog/<actor>/YYYY-MM/YYYYMMDDTHHMM-title.md
2587
+ \`\`\`
2588
+
2589
+ ## Rules
2590
+
2591
+ - Prefer creating new worklog files over appending shared core memory files.
2592
+ - Keep worklog entries short: 1-3 summary bullets, key files, verification.
2593
+
2594
+ ## Recent Work
2595
+
2596
+ ${worklogItems}
2597
+ `);
2598
+
2599
+ const actorFiles = listMarkdownFiles(path.join(dir, '.memoc', 'actors'))
2600
+ .filter(fp => path.basename(fp) !== 'README.md')
2601
+ .sort();
2602
+ const actorItems = actorFiles.length
2603
+ ? actorFiles.map(fp => `- [${markdownTitle(safeRead(fp), path.basename(fp, '.md'))}](${path.basename(fp)})`).join('\n')
2604
+ : '_None yet. Use `memoc actor set <name>` or `memoc work "<title>"`._';
2605
+ write(actorsReadme, `# Actors
2606
+
2607
+ Generated actor index for this shared repo.
2608
+
2609
+ ## Actor Detection
2610
+
2611
+ 1. \`MEMOC_ACTOR\`
2612
+ 2. \`.memoc/local/actor\` set by \`memoc actor set <name>\`
2613
+ 3. \`git config user.name\`
2614
+ 4. \`git config user.email\`
2615
+ 5. OS username
2616
+
2617
+ \`.memoc/local/\` is ignored by git so each machine can keep its own actor setting.
2618
+
2619
+ ## Actors
2620
+
2621
+ ${actorItems}
2622
+ `);
2623
+ }
2624
+
2165
2625
  // ═══════════════════════════════════════════════════════════════════
2166
2626
  // WIKI OPERATIONS — lint, ingest, and durable topic notes
2167
2627
  // ═══════════════════════════════════════════════════════════════════
@@ -2285,8 +2745,6 @@ function runIngest(dir) {
2285
2745
  addWikiListItem(path.join(dir, '.memoc', 'wiki', 'sources.md'), 'Source Records', pathRelativeMarkdown(path.join(dir, '.memoc', 'wiki'), sourcePath), title, 'needs synthesis');
2286
2746
  addWikiListItem(path.join(dir, '.memoc', 'wiki', 'sources', 'README.md'), 'Source Records', path.basename(sourcePath), title, 'source record');
2287
2747
  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
2748
  console.log('\n memoc ingest\n');
2291
2749
  console.log(` Source record ${normRel(dir, sourcePath)}`);
2292
2750
  console.log(` Raw reference ${rawDisplay}`);
@@ -2315,8 +2773,6 @@ function runNote(dir) {
2315
2773
  ensureMemocFrontmatter(topicPath, dir);
2316
2774
  addWikiListItem(path.join(dir, '.memoc', 'wiki', 'topics', 'README.md'), 'Topic Pages', path.basename(topicPath), title, 'topic note');
2317
2775
  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
2776
  console.log('\n memoc note\n');
2321
2777
  console.log(` Topic ${normRel(dir, topicPath)}`);
2322
2778
  console.log(' Next Link related sources/topics, then run memoc lint-wiki.');
@@ -2334,7 +2790,9 @@ function ensureMemocBase(dir) {
2334
2790
  [path.join(memDir, 'raw/README.md'), tplRawReadme],
2335
2791
  [path.join(memDir, 'raw/files/README.md'), tplRawFilesReadme],
2336
2792
  [path.join(memDir, 'raw/urls/README.md'), tplRawUrlsReadme],
2337
- [path.join(memDir, 'log.md'), tplLog],
2793
+ [path.join(memDir, 'activity.md'), tplActivity],
2794
+ [path.join(memDir, 'actors/README.md'), tplActorsReadme],
2795
+ [path.join(memDir, 'worklog/README.md'), tplWorklogReadme],
2338
2796
  [path.join(memDir, '00-agent-index.md'), () => tplAgentIndex(p)],
2339
2797
  ];
2340
2798
  for (const [fp, tpl] of files) ensure(fp, tpl());
@@ -2488,12 +2946,6 @@ function addWikiListItem(filePath, heading, link, title, note) {
2488
2946
  write(filePath, src.replace(re, `$1${replacementBody}`));
2489
2947
  }
2490
2948
 
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
2949
  // ═══════════════════════════════════════════════════════════════════
2498
2950
  // SEARCH
2499
2951
  // ═══════════════════════════════════════════════════════════════════
@@ -2703,7 +3155,7 @@ function searchPriority(file, scope = 'memory') {
2703
3155
  '.memoc/04-handoff.md',
2704
3156
  '.memoc/06-project-rules.md',
2705
3157
  '.memoc/03-decisions.md',
2706
- '.memoc/log.md',
3158
+ '.memoc/activity.md',
2707
3159
  'AGENTS.md',
2708
3160
  'CLAUDE.md',
2709
3161
  'llms.txt',
@@ -2737,7 +3189,7 @@ function runTokens(dir) {
2737
3189
  ['03-decisions.md', path.join(memDir, '03-decisions.md')],
2738
3190
  ['04-handoff.md', path.join(memDir, '04-handoff.md')],
2739
3191
  ['06-project-rules.md', path.join(memDir, '06-project-rules.md')],
2740
- ['log.md', path.join(memDir, 'log.md')],
3192
+ ['activity.md', path.join(memDir, 'activity.md')],
2741
3193
  ];
2742
3194
 
2743
3195
  console.log('\n memoc tokens\n');
@@ -2770,13 +3222,62 @@ function runTokens(dir) {
2770
3222
  const summaryContent = read(path.join(memDir, 'session-summary.md'));
2771
3223
  const summaryBytes = Buffer.byteLength(summaryContent, 'utf8');
2772
3224
  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.`);
3225
+ 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.`);
3226
+ }
3227
+ console.log();
3228
+ }
3229
+
3230
+ // ═══════════════════════════════════════════════════════════════════
3231
+ // DOCTOR — quick health checks for shared memoc repos
3232
+ // ═══════════════════════════════════════════════════════════════════
3233
+
3234
+ function runDoctor(dir) {
3235
+ const issues = [];
3236
+ const warnings = [];
3237
+ const memDir = path.join(dir, '.memoc');
3238
+ const summaryPath = path.join(memDir, 'session-summary.md');
3239
+ const summary = safeRead(summaryPath);
3240
+ if (!summary) issues.push('Missing .memoc/session-summary.md');
3241
+ else if (Buffer.byteLength(summary, 'utf8') > 800) warnings.push('session-summary.md exceeds 800B; run memoc trim-summary');
3242
+
3243
+ for (const fp of [
3244
+ path.join(dir, '.memoc', 'bin', 'memoc'),
3245
+ path.join(dir, '.memoc', 'bin', 'memoc.cmd'),
3246
+ path.join(dir, '.memoc', 'bin', 'memoc.ps1'),
3247
+ ]) {
3248
+ const src = safeRead(fp);
3249
+ if (src && (/C:\\Users\\|\/Users\/[^/]+\/\.local\/share\/memoc\/runtime/.test(src))) {
3250
+ issues.push(`${normRel(dir, fp)} contains a user-specific runtime path; run memoc upgrade`);
3251
+ }
3252
+ }
3253
+
3254
+ for (const fp of collectMemocMarkdownFiles(dir)) {
3255
+ const src = safeRead(fp);
3256
+ const fenceCount = (src.match(/^---$/gm) || []).length;
3257
+ if (fenceCount > 2) warnings.push(`${normRel(dir, fp)} may have nested frontmatter; run memoc upgrade`);
3258
+ }
3259
+
3260
+ const actor = detectActor(dir);
3261
+ if (actor.actor === 'unknown') warnings.push('Actor could not be detected; run memoc actor set <name>');
3262
+
3263
+ console.log('\n memoc doctor\n');
3264
+ console.log(` Actor ${actor.actor} (${actor.source})`);
3265
+ console.log(` Issues ${issues.length}`);
3266
+ console.log(` Warnings ${warnings.length}`);
3267
+ if (issues.length) {
3268
+ console.log('\n Issues:');
3269
+ for (const issue of issues) console.log(` - ${issue}`);
3270
+ }
3271
+ if (warnings.length) {
3272
+ console.log('\n Warnings:');
3273
+ for (const warning of warnings.slice(0, 20)) console.log(` - ${warning}`);
2774
3274
  }
3275
+ if (!issues.length && !warnings.length) console.log('\n Looks good.');
2775
3276
  console.log();
2776
3277
  }
2777
3278
 
2778
3279
  // ═══════════════════════════════════════════════════════════════════
2779
- // COMPRESS — archive old log.md entries to keep file small
3280
+ // COMPRESS — legacy log.md archiver
2780
3281
  // ═══════════════════════════════════════════════════════════════════
2781
3282
 
2782
3283
  function runCompress(dir) {
@@ -2812,6 +3313,7 @@ function runCompress(dir) {
2812
3313
  write(logPath, header.trimEnd() + '\n' + toKeep.join('') + '\n');
2813
3314
 
2814
3315
  console.log(`\n memoc compress\n`);
3316
+ console.log(' Legacy command: new activity should use .memoc/worklog/ instead of log.md.');
2815
3317
  console.log(` Archived ${toArchive.length} entries → .memoc/log-archive.md`);
2816
3318
  console.log(` Kept ${toKeep.length} recent entries in log.md`);
2817
3319
  const saved = Buffer.byteLength(toArchive.join(''), 'utf8');
@@ -2851,12 +3353,11 @@ function runTrimSummary(dir) {
2851
3353
  : '# Session Summary Archive\n\nOlder oversized startup summaries moved by `memoc trim-summary`.\n';
2852
3354
  fs.appendFileSync(archivePath, `${archiveHeader}\n## [${nowISO()}] archived summary (${beforeBytes}B)\n\n${src.trimEnd()}\n`, 'utf8');
2853
3355
  write(summaryPath, compact);
2854
- appendMemocLog(dir, `trim-summary | Archived oversized session summary (${beforeBytes}B → ${afterBytes}B).`);
2855
3356
 
2856
3357
  console.log('\n memoc trim-summary\n');
2857
3358
  console.log(` Archived .memoc/session-summary-archive.md`);
2858
3359
  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.');
3360
+ console.log(' Reminder Completed history belongs in worklog; resume details belong in 04-handoff.md.');
2860
3361
  console.log('\n Done.\n');
2861
3362
  }
2862
3363
 
@@ -2866,7 +3367,7 @@ function compactSessionSummary(src) {
2866
3367
  '# Session Summary',
2867
3368
  `Last: ${nowISO()}`,
2868
3369
  '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`.',
3370
+ 'Completed history belongs in actor worklogs; incomplete/risky resume detail belongs in `04-handoff.md`.',
2870
3371
  '',
2871
3372
  ];
2872
3373
 
@@ -2980,8 +3481,12 @@ if (!cmd || cmd === '--help' || cmd === '-h' || cmd === 'help') {
2980
3481
  console.log(' summary Print a tiny status/resume overview');
2981
3482
  console.log(' tokens Estimate token cost of current memory files');
2982
3483
  console.log(' trim-summary Archive and compact oversized session-summary.md');
2983
- console.log(' compress Archive old log.md entries to keep file small');
3484
+ console.log(' compress Legacy: archive old log.md entries');
2984
3485
  console.log(' add <agent> Add entry file for a specific agent (run without args to list)');
3486
+ console.log(' actor [set <name>] Show or set the local memoc actor');
3487
+ console.log(' work "<title>" Create a conflict-light actor worklog entry');
3488
+ console.log(' activity List recent memoc worklog entries');
3489
+ console.log(' doctor Check common memoc health issues');
2985
3490
  console.log(' search "<query>" Search memory/agent docs (use --snippets for line matches)');
2986
3491
  console.log(' grep "<query>" Search project source/text files (use --snippets for line matches)');
2987
3492
  console.log(' ingest <path|url> Create a raw/source record scaffold for wiki synthesis');
@@ -3005,6 +3510,10 @@ if (cmd === 'tokens') { runTokens(cwd); process.exit(0); }
3005
3510
  if (cmd === 'trim-summary') { runTrimSummary(cwd); process.exit(0); }
3006
3511
  if (cmd === 'compress') { runCompress(cwd); process.exit(0); }
3007
3512
  if (cmd === 'add') { runAdd(cwd); process.exit(0); }
3513
+ if (cmd === 'actor') { runActor(cwd); process.exit(0); }
3514
+ if (cmd === 'work') { runWork(cwd); process.exit(0); }
3515
+ if (cmd === 'activity') { runActivity(cwd); process.exit(0); }
3516
+ if (cmd === 'doctor') { runDoctor(cwd); process.exit(0); }
3008
3517
  if (cmd === 'search') { runSearch(cwd, 'memory'); process.exit(0); }
3009
3518
  if (cmd === 'grep') { runSearch(cwd, 'project'); process.exit(0); }
3010
3519
  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.10",
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",