Skip to content

Commit 2a210ae

Browse files
committed
feat(dogfood): wire PostToolUse+Stop hooks + weekly rollup cron
- .claude/settings.json: project-scope hooks fire only in this repo - scripts/hooks/dogfood-hook.py: stdlib-only, filters mcp__vault-mind* tool calls and session-end events; appends tab-separated records to .dogfood.log; non-blocking (exit 0 on error) - scripts/weekly-review.ps1: Sunday 03:00 rollup, reads last 7d, groups by op, appends dated block to .dogfood.weekly.md (gitignored) - Task Scheduler entry obsidian-llm-wiki-weekly registered via Register-ScheduledTask (State=Ready) - Smoke tested: vault-mind calls logged, non-vault tools filtered, weekly rollup produces correct tally Closes Week 2 Track A (dogfood hard-start) per breezy-stargazing-chipmunk roadmap. Track A provides ground-truth usage data required by the 12-week DoD (Curry 3+ daily calls, identify dead modules at Week 12).
1 parent 5f2dccb commit 2a210ae

5 files changed

Lines changed: 173 additions & 2 deletions

File tree

.claude/settings.json

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
{
2+
"$schema": "https://json.schemastore.org/claude-code-settings.json",
3+
"hooks": {
4+
"PostToolUse": [
5+
{
6+
"matcher": "mcp__vault-mind.*",
7+
"hooks": [
8+
{
9+
"type": "command",
10+
"command": "python scripts/hooks/dogfood-hook.py posttool"
11+
}
12+
]
13+
}
14+
],
15+
"Stop": [
16+
{
17+
"hooks": [
18+
{
19+
"type": "command",
20+
"command": "python scripts/hooks/dogfood-hook.py stop"
21+
}
22+
]
23+
}
24+
]
25+
}
26+
}

.gitignore

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,3 +29,5 @@ fm_work/
2929

3030
# Dogfood usage log (per-dev, not shared)
3131
.dogfood.log
32+
33+
.dogfood.weekly.md

progress.txt

Lines changed: 31 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -271,8 +271,37 @@ All awaiting activation (need credentials). X needs X_BEARER_TOKEN.
271271
- main/master branch decision (unchanged, still deferred)
272272
- Optionally push Gitea mirror
273273

274+
## 2026-04-19 -- Week 2 Track A dogfood hard-start COMPLETE
275+
276+
### What done
277+
278+
- `.claude/settings.json` (project-scope, committed): registers two hooks
279+
- PostToolUse matcher `mcp__vault-mind.*` -> logs tool call + query
280+
- Stop -> logs session-end with 8-char session_id prefix
281+
- `scripts/hooks/dogfood-hook.py` (stdlib-only, zero-dep):
282+
- Modes: `posttool` filters non-vault-mind tools, extracts query/path/id
283+
- Mode `stop` records session end
284+
- Non-blocking: exits 0 on any error (never breaks Claude Code)
285+
- Output: tab-separated `ts\ttool\top\tnote` appended to `.dogfood.log`
286+
- `scripts/weekly-review.ps1`: reads last 7d of `.dogfood.log`, groups by op,
287+
appends dated block to `.dogfood.weekly.md` (gitignored)
288+
- Windows scheduled task `obsidian-llm-wiki-weekly` registered via
289+
`Register-ScheduledTask` (State=Ready, weekly Sunday 03:00 local)
290+
- Smoke test: 2 vault-mind calls + 2 session-end events logged; non-vault-mind
291+
Read call correctly filtered out; weekly-review.ps1 produced rollup
292+
- `.gitignore`: add `.dogfood.weekly.md`
293+
294+
### Outstanding (Week 2 remainder)
295+
296+
- `/vault-save` session-end binding (separate from dogfood log -- actually writes
297+
a recap note to the vault). Not done this session.
298+
- Gitea mirror push (optional)
299+
- main/master branch strategy decision (still deferred)
300+
274301
## Next step
275302

276-
- Execute Phase B1-B5 + A0 (README, SEO, outreach drafts, dogfood log)
303+
- Track C Compiler: `compiler/link_discovery.py` skeleton
304+
- scan 911 md files in E:/knowledge, LLM-suggest missing `[[wikilinks]]`,
305+
write proposals to `.compile/link_suggestions.md`
306+
- Then `compiler/concept_graph.py` v0 producing `graph.json`
277307
- Roadmap file: C:/Users/Administrator/.claude/plans/breezy-stargazing-chipmunk.md
278-
- After Week 1: Track C Compiler work (link_discovery + concept_graph)

scripts/hooks/dogfood-hook.py

Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,59 @@
1+
#!/usr/bin/env python3
2+
# dogfood-hook: append usage records to .dogfood.log for Week-12 pruning analysis.
3+
# Fires from project .claude/settings.json on PostToolUse (mcp__vault-mind*) and Stop.
4+
# Stdlib-only, zero-dep. Non-blocking: exits 0 on any error.
5+
6+
import json
7+
import sys
8+
from datetime import datetime, timezone
9+
from pathlib import Path
10+
11+
ROOT = Path(__file__).resolve().parents[2]
12+
LOG = ROOT / ".dogfood.log"
13+
14+
15+
def main() -> None:
16+
mode = sys.argv[1] if len(sys.argv) > 1 else "unknown"
17+
try:
18+
payload = json.loads(sys.stdin.read() or "{}")
19+
except Exception:
20+
payload = {}
21+
22+
ts = datetime.now(timezone.utc).strftime("%Y-%m-%dT%H:%M:%SZ")
23+
tool = "-"
24+
op = "-"
25+
note = ""
26+
27+
if mode == "posttool":
28+
name = payload.get("tool_name", "")
29+
if not name.startswith("mcp__vault-mind"):
30+
return
31+
tool = "vault-mind"
32+
# strip mcp__vault-mind__ or mcp__vault-mind-connector__
33+
op = name.split("__", 2)[-1] if "__" in name else name
34+
tool_input = payload.get("tool_input", {}) or {}
35+
if isinstance(tool_input, dict):
36+
q = tool_input.get("query") or tool_input.get("path") or tool_input.get("id")
37+
if q:
38+
note = str(q)[:120].replace("\t", " ").replace("\n", " ")
39+
elif mode == "stop":
40+
tool = "session"
41+
op = "session-end"
42+
sid = payload.get("session_id", "")
43+
note = sid[:8] if sid else ""
44+
else:
45+
return
46+
47+
line = f"{ts}\t{tool}\t{op}\t{note}\n"
48+
try:
49+
with LOG.open("a", encoding="utf-8") as f:
50+
f.write(line)
51+
except Exception:
52+
pass
53+
54+
55+
if __name__ == "__main__":
56+
try:
57+
main()
58+
except Exception:
59+
pass

scripts/weekly-review.ps1

Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,55 @@
1+
# weekly-review.ps1 -- Sunday 03:00 dogfood rollup.
2+
# Reads .dogfood.log, tallies op counts for the past 7 days,
3+
# appends a dated block to .dogfood.weekly.md (gitignored).
4+
#
5+
# Registered via: schtasks /Create /TN obsidian-llm-wiki-weekly /SC WEEKLY /D SUN /ST 03:00 ...
6+
7+
$ErrorActionPreference = 'Stop'
8+
$Root = Split-Path -Parent $PSScriptRoot
9+
$Log = Join-Path $Root '.dogfood.log'
10+
$Out = Join-Path $Root '.dogfood.weekly.md'
11+
12+
if (-not (Test-Path $Log)) {
13+
"dogfood log missing: $Log" | Write-Error
14+
exit 1
15+
}
16+
17+
$cutoff = (Get-Date).ToUniversalTime().AddDays(-7)
18+
$week = Get-Date -Format "yyyy-'W'ww"
19+
$stamp = (Get-Date).ToUniversalTime().ToString("yyyy-MM-ddTHH:mm:ssZ")
20+
21+
$records = Get-Content -LiteralPath $Log -Encoding UTF8 |
22+
Where-Object { $_ -and -not $_.StartsWith('#') } |
23+
ForEach-Object {
24+
$parts = $_ -split "`t"
25+
if ($parts.Length -ge 3) {
26+
[pscustomobject]@{
27+
ts = $parts[0]
28+
tool = $parts[1]
29+
op = $parts[2]
30+
}
31+
}
32+
} |
33+
Where-Object {
34+
try { [datetime]::Parse($_.ts).ToUniversalTime() -ge $cutoff } catch { $false }
35+
}
36+
37+
$total = ($records | Measure-Object).Count
38+
$tally = $records | Group-Object op | Sort-Object Count -Descending
39+
40+
$lines = @()
41+
$lines += "## $week (generated $stamp)"
42+
$lines += ""
43+
$lines += "- total records (last 7d): $total"
44+
if ($total -gt 0) {
45+
$lines += "- top ops:"
46+
foreach ($g in $tally) {
47+
$lines += " - $($g.Name): $($g.Count)"
48+
}
49+
} else {
50+
$lines += "- (no records in window)"
51+
}
52+
$lines += ""
53+
54+
Add-Content -LiteralPath $Out -Value $lines -Encoding UTF8
55+
Write-Output "wrote $total records to $Out"

0 commit comments

Comments
 (0)