feat(log): add append-only activity journal (log.md)#85
Conversation
Implements the log.md journal from Karpathy's llm-wiki gist: an append-only record of what happened and when, written by ingest, compile, query, and lint. Each entry is a grep-able heading — `## [YYYY-MM-DDThh:mm:ssZ] operation | description` (ISO 8601 UTC) — followed by a markdown bullet body with page wikilinks and counts. Only headings start with `## [`, so the gist's recipe `grep "^## \[" log.md | tail -5` still returns the most recent operations. Per-operation detail: - ingest: Source (input), Saved (output file), Chars - compile: Sources consumed, Created vs Updated pages, Deleted count - query: cited Pages - lint: error/warning/info counts, Flagged pages Logging lives in the core functions (ingestSource, runCompilePipeline, generateAnswer, lint) so both the CLI and the MCP server are covered. Writes are resilient — a log failure warns but never breaks the operation it records. log.md is gitignored alongside wiki/ and sources/. Token/cost intentionally deferred to a follow-up. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
5ca4429 to
f153836
Compare
ethanj
left a comment
There was a problem hiding this comment.
Thanks for putting this together. The core idea is useful, and the implementation is cleanly documented. I especially like that the log format keeps the gist’s grep "^## \\[" log.md | tail -5 workflow intact while still allowing richer detail lines.
I’d like to request a few changes before merging:
-
lint_wikinow mutates state, which conflicts with the MCP server contract.src/mcp/server.tscurrently tells agents thatread_page,lint_wiki,wiki_status, and fast unrecorded eval “do not mutate state,” butlint()now appends tolog.mdon every run, including clean 0/0/0 passes. Could we either avoid journaling lint from that read-only path, or deliberately update the contract and add a regression test showinglint_wikiis expected to writelog.md? -
The compile created/updated split is re-derived from a bare-slug directory scan.
listExistingPageSlugs()mergeswiki/concepts/*.mdandwiki/queries/*.mdinto oneSet<string>, so an existing query page and a newly-created concept with the same slug can be misclassified as “Updated.” It would be better to thread the created/updated result from the exact page write path, or at least track namespaced IDs likeconcepts/fooandqueries/foo. -
The actual journaling call sites need integration coverage.
test/activity-log.test.tscovers the formatter and append helper, but not the real compile/lint/query/ingest paths. A small CLI-level test would make sure the feature keeps working through the user-facing surface and would catch issues like lint mutating unexpectedly or compile misreporting created vs updated pages.
A few smaller non-blocking notes:
generateAnswer()logs the query before the answer is generated, so a later LLM failure still records a query entry. That may be fine if the journal records attempts, but it is worth making that intentional because compile logs after successful finalization.ingestSource()writes the log usingprocess.cwd(), while compile/query/lint pass an explicit root. It works for the current CLI/MCP path, but passing root explicitly would make the invariant less fragile.produced = [...pages, ...seedSlugs]is not deduped, so a collision could inflate the compile page count.
Good feature overall. Once the lint contract and compile classification are tightened, I think this will fit the repo well.
…fication, add integration tests Responds to review feedback on the log.md journal: 1. lint stays read-only. Move lint journaling out of the core lint() function (which the MCP `lint_wiki` tool calls and documents as non-mutating) into the CLI command. MCP lint no longer writes log.md; the CLI still does. Regression test added for both paths. 2. Fix compile created/updated misclassification. listExistingPageIds now namespaces snapshot IDs by directory (wiki/concepts/foo vs wiki/queries/foo), so a query page and a same-slug new concept are no longer conflated as "Updated". Compile only writes concept-namespace pages, so existence is checked against that namespace. 3. Add integration coverage for the real journaling call sites: CLI lint, read-only core lint(), CLI ingest, and the compile created-vs-updated split across two compiles. Also from the non-blocking notes: - query now journals after the answer is produced (matching compile, which logs after finalization), so a mid-flight LLM failure records nothing. - dedupe produced page slugs before counting/splitting, so a concept/seed slug collision can't inflate the compile page count. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
|
Thanks for the thorough review — all three blocking items addressed in 1. 2. Created/Updated misclassification ✅ 3. Integration coverage for the call sites ✅
Non-blocking notes:
|
Drop lint from the activity journal entirely. log.md now records only ingest, compile, and query. lint is a read-only check (and the MCP lint_wiki tool is documented as non-mutating), so it should leave no trace in the journal. Removes the lint journaling from the CLI command, updates docs, and flips the integration test to assert `llmwiki lint` writes no log.md. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
|
Follow-up ( |
ethanj
left a comment
There was a problem hiding this comment.
Thanks for the thorough follow-up. I re-reviewed the latest changes and this is in good shape now.
The main concerns from the earlier pass are addressed: lint_wiki preserves the no-mutation contract again, the created/updated split is now namespaced so concept/query slug collisions do not contaminate the journal, and the real call sites are covered through integration tests instead of only testing the formatter/helper layer.
The end-to-end coverage is the important part here. Pinning lint as read-only, ingest details, and first-compile-created / second-compile-updated behavior gives this feature the right protection going forward.
Approving. Nice work on the journal.
Reconcile the SDK's status/page extraction with #88's MCP freshness work: - Unify collectStatus on #88's richer freshness-derived implementation, relocated into the shared src/status/collect.ts (consumed by both MCP and SDK); drop the now-redundant src/mcp/status.ts and repoint #88's tests at the shared module. - Resolve src/mcp/tools.ts to use the extracted page-read/retrieval modules (the extracted readPageRecord already carries #88's safe-title handling). - Journal ingests under the project root with a root-relative saved path, fixing the interaction between #85's activity journal and the root-explicit ingest change.
What
Adds the
log.mdjournal from Karpathy's llm-wiki gist: an append-only record of what happened and when, written automatically byingest,compile,query, andlint.Each entry is a grep-able heading followed by a markdown bullet body:
Why
The gist treats
log.mdas a companion toindex.md: where the index organizes content for discovery, the log tracks temporal progression. The project had no such journal.Design notes
## [YYYY-MM-DDThh:mm:ssZ] operation | description(ISO 8601 UTC) + a- detailbullet body. Only headings start with## [, so the gist's recipegrep "^## \[" log.md | tail -5still returns the most recent operations even with bodies.ingest—Source(input),Saved(output file),Charscompile—Sourcesconsumed,CreatedvsUpdatedpages (split via a pre-generation on-disk snapshot),Deleted sourcescountquery— citedPageslint— error/warning/info counts +FlaggedpagesingestSource,runCompilePipeline,generateAnswer,lint), so both the CLI and the MCP server are covered. It writes tolog.mdat the project root (not underwiki/) becauseingestruns before anywiki/exists.log.mdis gitignored alongside/wiki/and/sources/.(+N more)suffix.Out of scope
Token/cost was intentionally deferred to a follow-up (pairs naturally with the Claude Agent SDK provider, which reports usage/cost natively).
Testing
test/activity-log.test.tscovers heading format, ISO timestamp, bullet body, wikilink/list truncation, append-only accumulation, and the never-throws guarantee.npx tsc --noEmit,npm run build,npm test(1216 passed), andfallow(no dead code/duplication, 0 above threshold, maintainability 91.5) all pass.ingest → compile → query → lintall journal correctly, including the Created/Updated split across two compiles.🤖 Generated with Claude Code