@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.
- package/README.md +40 -6
- package/bin/cli.js +668 -93
- 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
|
-
#
|
|
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
|
|
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
|
-
|
|
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/
|
|
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** —
|
|
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
|
|
237
|
-
|
|
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
|
|
241
|
-
|
|
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
|
|
245
|
-
|
|
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
|
-
|
|
417
|
+
ensureRuntimeInstall(mark);
|
|
278
418
|
const files = [
|
|
279
|
-
[path.join(dir, '.memoc', 'bin', 'memoc.cmd'),
|
|
280
|
-
[path.join(dir, '.memoc', 'bin', 'memoc.ps1'),
|
|
281
|
-
[path.join(dir, '.memoc', 'bin', 'memoc'),
|
|
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
|
-
|
|
436
|
+
ensureRuntimeInstall(mark);
|
|
437
|
+
writeLaunchers(userBin, mark, 'user bin');
|
|
297
438
|
return userBin;
|
|
298
439
|
}
|
|
299
440
|
|
|
300
|
-
function writeLaunchers(binDir, mark, label
|
|
441
|
+
function writeLaunchers(binDir, mark, label) {
|
|
301
442
|
const files = [
|
|
302
|
-
[path.join(binDir, 'memoc.cmd'),
|
|
303
|
-
[path.join(binDir, 'memoc.ps1'),
|
|
304
|
-
[path.join(binDir, 'memoc'),
|
|
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
|
-
|
|
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
|
-
- [ ]
|
|
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 =
|
|
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
|
|
1041
|
+
return `${nextFm}\n${rest.replace(/^\r?\n/, '')}`;
|
|
884
1042
|
}
|
|
885
1043
|
|
|
886
1044
|
function parseYamlFrontmatter(src) {
|
|
887
|
-
|
|
888
|
-
const
|
|
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
|
-
- [
|
|
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/
|
|
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/
|
|
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
|
|
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\`, \`
|
|
1231
|
-
| Code, config, data, or assets changed | \`02-current-project-state.md\`, relevant \`systems/*.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\`, \`
|
|
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
|
-
|
|
|
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
|
-
- \`
|
|
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/
|
|
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
|
|
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
|
-
|
|
1525
|
+
Legacy file. New memoc activity belongs in actor worklogs under \`.memoc/worklog/\`.
|
|
1364
1526
|
|
|
1365
|
-
|
|
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
|
|
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
|
|
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.
|
|
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
|
-
-
|
|
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
|
|
1784
|
-
- Move completed session details out of \`session-summary.md\` into
|
|
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
|
|
2137
|
+
const entries = ['.memoc/.pending', '.memoc/local/'];
|
|
1894
2138
|
const gitignoreContent = fs.existsSync(gitignorePath)
|
|
1895
2139
|
? fs.readFileSync(gitignorePath, 'utf8') : '';
|
|
1896
|
-
const
|
|
1897
|
-
|
|
1898
|
-
|
|
1899
|
-
|
|
1900
|
-
|
|
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 (
|
|
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, '
|
|
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
|
-
//
|
|
2290
|
+
// Generated memory maps — replace 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
|
-
//
|
|
2077
|
-
const
|
|
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, '
|
|
2085
|
-
[path.join(memDir, '
|
|
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
|
-
|
|
2110
|
-
|
|
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
|
-
|
|
2119
|
-
|
|
2120
|
-
|
|
2121
|
-
|
|
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, '
|
|
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/
|
|
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
|
-
['
|
|
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
|
|
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 —
|
|
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
|
|
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
|
|
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
|
|
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); }
|