@suwujs/codex-vault 0.6.0 → 0.7.0

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 CHANGED
@@ -63,20 +63,20 @@ git commit (persistent)
63
63
  Next session → session-start hook injects context back
64
64
  ```
65
65
 
66
- Three hooks power the loop:
66
+ Hooks power the loop:
67
67
 
68
- | Hook | When | What |
69
- |------|------|------|
70
- | **session-start** | Agent starts | Injects North Star goals, recent git changes, active work, vault file listing |
71
- | **classify-message** | Every message | Detects decisions, wins, project updates — hints the agent where to file them |
72
- | **validate-write** | After writing `.md` | Checks frontmatter and wikilinks — catches mistakes before they stick |
68
+ | Hook | When | What | Claude Code | Codex CLI |
69
+ |------|------|------|-------------|-----------|
70
+ | **session-start** | Agent starts | Injects North Star goals, recent git changes, active work, vault file listing | SessionStart | SessionStart |
71
+ | **classify-message** | Every message | Detects decisions, wins, project updates — hints the agent where to file them | UserPromptSubmit | UserPromptSubmit |
72
+ | **validate-write** | After writing `.md` | Checks frontmatter and wikilinks — catches mistakes before they stick | PostToolUse (Write\|Edit) | N/A (Codex only supports Bash) |
73
73
 
74
74
  ## Supported Agents
75
75
 
76
76
  | Agent | Hooks | Skills | Status |
77
77
  |-------|-------|--------|--------|
78
78
  | Claude Code | 3 hooks via `.claude/settings.json` | `/dump` `/recall` `/ingest` `/wrap-up` | Full support |
79
- | Codex CLI | 3 hooks via `.codex/hooks.json` | `$dump` `$recall` `$ingest` `$wrap-up` | Full support |
79
+ | Codex CLI | 2 hooks via `.codex/hooks.json` | `$dump` `$recall` `$ingest` `$wrap-up` | Full support (PostToolUse limited to Bash by Codex) |
80
80
  | Other | Write an adapter ([docs/adding-an-agent.md](docs/adding-an-agent.md)) | Depends on agent | Community |
81
81
 
82
82
  ## Vault Structure
package/README.zh-CN.md CHANGED
@@ -95,20 +95,20 @@ git commit(持久化)
95
95
  下次 session → session-start hook 注入上下文
96
96
  ```
97
97
 
98
- 三个 hook 驱动整个循环:
98
+ Hook 驱动整个循环:
99
99
 
100
- | Hook | 触发时机 | 做什么 |
101
- |------|---------|--------|
102
- | **session-start** | Agent 启动 | 注入 North Star 目标、近期 git 变更、活跃项目、vault 文件清单 |
103
- | **classify-message** | 每条消息 | 检测决策、成果、项目更新 — 提示 agent 该归档到哪里 |
104
- | **validate-write** | 写 `.md` 后 | 检查 frontmatter 和 wikilinks — 在落盘前纠错 |
100
+ | Hook | 触发时机 | 做什么 | Claude Code | Codex CLI |
101
+ |------|---------|--------|-------------|-----------|
102
+ | **session-start** | Agent 启动 | 注入 North Star 目标、近期 git 变更、活跃项目、vault 文件清单 | SessionStart | SessionStart |
103
+ | **classify-message** | 每条消息 | 检测决策、成果、项目更新 — 提示 agent 该归档到哪里 | UserPromptSubmit | UserPromptSubmit |
104
+ | **validate-write** | 写 `.md` 后 | 检查 frontmatter 和 wikilinks — 在落盘前纠错 | PostToolUse (Write\|Edit) | 不支持(Codex 仅支持 Bash) |
105
105
 
106
106
  ## 支持的 Agent
107
107
 
108
108
  | Agent | Hooks | Skills | 状态 |
109
109
  |-------|-------|--------|------|
110
110
  | Claude Code | `.claude/settings.json` 3 hooks | `/dump` `/recall` `/ingest` `/wrap-up` | 完整支持 |
111
- | Codex CLI | `.codex/hooks.json` 3 hooks | `$dump` `$recall` `$ingest` `$wrap-up` | 完整支持 |
111
+ | Codex CLI | `.codex/hooks.json` 2 hooks | `$dump` `$recall` `$ingest` `$wrap-up` | 完整支持(PostToolUse 受 Codex 限制仅支持 Bash) |
112
112
  | 其他 | 写适配器([指南](docs/adding-an-agent.md)) | 取决于 agent | 社区贡献 |
113
113
 
114
114
  ## Vault 结构
@@ -0,0 +1,28 @@
1
+ #!/bin/bash
2
+ # Codex-Vault wrapper — shows vault banner then launches Codex CLI
3
+ # Usage: codex-vault-run [codex args...]
4
+
5
+ SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
6
+ PROJECT_DIR="$(pwd)"
7
+
8
+ # Find session-start hook relative to project or plugin
9
+ HOOK=""
10
+ for candidate in \
11
+ "$PROJECT_DIR/plugin/hooks/codex/session-start.py" \
12
+ "$PROJECT_DIR/.codex-vault/hooks/codex/session-start.py" \
13
+ "$SCRIPT_DIR/../plugin/hooks/codex/session-start.py"; do
14
+ if [ -f "$candidate" ]; then
15
+ HOOK="$candidate"
16
+ break
17
+ fi
18
+ done
19
+
20
+ if [ -n "$HOOK" ]; then
21
+ SUMMARY=$(echo '{}' | python3 "$HOOK" 2>/dev/null \
22
+ | python3 -c "import sys,json; d=json.load(sys.stdin); print(d.get('systemMessage',''))" 2>/dev/null)
23
+ if [ -n "$SUMMARY" ]; then
24
+ echo " $SUMMARY"
25
+ fi
26
+ fi
27
+
28
+ exec codex "$@"
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@suwujs/codex-vault",
3
- "version": "0.6.0",
3
+ "version": "0.7.0",
4
4
  "description": "Persistent knowledge vault for LLM agents (Claude Code, Codex CLI)",
5
5
  "license": "MIT",
6
6
  "repository": {
@@ -283,16 +283,14 @@ def main():
283
283
  icon = "\U0001f504" if mode == "auto" else "\U0001f4a1"
284
284
  label = ", ".join(feedback_parts) if feedback_parts else "intent detected"
285
285
 
286
- # Codex CLI: use stderr for feedback (no systemMessage rendering)
287
- sys.stderr.write(f" {icon} vault: {label}\n")
288
-
286
+ # hookSpecificOutput + additionalContext injected into LLM context
289
287
  output = {
290
288
  "hookSpecificOutput": {
291
289
  "hookEventName": "UserPromptSubmit",
292
290
  "additionalContext": context
293
- },
291
+ }
294
292
  }
295
- json.dump(output, sys.stdout)
293
+ sys.stdout.write(json.dumps(output) + "\n")
296
294
  sys.stdout.flush()
297
295
 
298
296
  sys.exit(0)
@@ -351,7 +351,7 @@ def main():
351
351
  "additionalContext": "## Session Context\n\n(No vault found)"
352
352
  }
353
353
  }
354
- json.dump(output, sys.stdout)
354
+ sys.stdout.write(json.dumps(output) + "\n")
355
355
  sys.exit(0)
356
356
 
357
357
  # Read hook input for session metadata
@@ -361,20 +361,33 @@ def main():
361
361
  event = {}
362
362
 
363
363
  context = _build_context(vault_dir)
364
- banner = _build_banner(vault_dir)
365
364
 
366
- # Codex CLI: systemMessage not rendered by TUI,
367
- # use stderr for terminal visibility (best effort)
368
- sys.stderr.write(banner + "\n")
365
+ # Build a short summary for user-facing display
366
+ goal = _north_star_goal(vault_dir)
367
+ active_dir = os.path.join(vault_dir, "work", "active")
368
+ work_count = len([f for f in os.listdir(active_dir) if f.endswith(".md")]) if os.path.isdir(active_dir) else 0
369
+ changes = _git_status_short(vault_dir)
370
+ all_files = _find_md_files(vault_dir)
371
+
372
+ summary_parts = ["[Vault]"]
373
+ branch = _run_git(["rev-parse", "--abbrev-ref", "HEAD"], vault_dir)
374
+ if branch:
375
+ summary_parts.append(branch[0])
376
+ if goal:
377
+ summary_parts.append(f"goal: {goal}")
378
+ summary_parts.append(f"active:{work_count}")
379
+ summary_parts.append(f"changes:{len(changes)}")
380
+ summary_parts.append(f"{len(all_files)} notes")
381
+ summary = " | ".join(summary_parts)
369
382
 
370
383
  output = {
371
384
  "hookSpecificOutput": {
372
385
  "hookEventName": "SessionStart",
373
386
  "additionalContext": context
374
387
  },
388
+ "systemMessage": summary
375
389
  }
376
-
377
- json.dump(output, sys.stdout)
390
+ sys.stdout.write(json.dumps(output) + "\n")
378
391
  sys.stdout.flush()
379
392
  sys.exit(0)
380
393
 
@@ -1,121 +1,113 @@
1
1
  #!/usr/bin/env python3
2
- """Post-write validation for vault notesCodex CLI version.
2
+ """PostToolUse hook for Codex CLIBash command validation.
3
3
 
4
- Checks frontmatter and wikilinks on any .md file written to the vault.
5
- Outputs hookSpecificOutput for Codex CLI. Feedback via stderr
6
- (Codex CLI does not render systemMessage).
7
-
8
- Note: Codex CLI PostToolUse only supports Bash matcher, so this script
9
- will only run when triggered by a Bash tool writing .md files. The
10
- validation logic is identical to the Claude Code version.
4
+ Checks Bash command output for hard failures (command not found,
5
+ permission denied, missing paths) and non-zero exit codes with
6
+ informative output. Modeled after oh-my-codex's native PostToolUse.
11
7
  """
12
8
  import json
13
9
  import re
14
10
  import sys
15
- import os
16
- from pathlib import Path
17
11
 
18
12
 
19
- def _check_log_format(content):
20
- """Validate log.md entry format: ## [YYYY-MM-DD] <type> | <title>"""
21
- warnings = []
22
- for i, line in enumerate(content.splitlines(), 1):
23
- if line.startswith("## ") and not line.startswith("## ["):
24
- # Heading that looks like a log entry but missing date brackets
25
- if any(t in line.lower() for t in ["ingest", "session", "query", "maintenance", "decision", "archive"]):
26
- warnings.append(f"Line {i}: log entry missing date format — expected `## [YYYY-MM-DD] <type> | <title>`")
27
- elif line.startswith("## ["):
28
- if not re.match(r"^## \[\d{4}-\d{2}-\d{2}\] \w+", line):
29
- warnings.append(f"Line {i}: malformed log entry — expected `## [YYYY-MM-DD] <type> | <title>`")
30
- return warnings
13
+ HARD_FAILURE_PATTERNS = re.compile(
14
+ r"command not found|permission denied|no such file or directory",
15
+ re.IGNORECASE,
16
+ )
17
+
18
+
19
+ def _safe_string(value):
20
+ return value if isinstance(value, str) else ""
21
+
22
+
23
+ def _safe_int(value):
24
+ if isinstance(value, int):
25
+ return value
26
+ if isinstance(value, str) and value.strip().lstrip("-").isdigit():
27
+ return int(value.strip())
28
+ return None
29
+
30
+
31
+ def _parse_tool_response(raw):
32
+ """Try to parse tool_response as JSON dict."""
33
+ if isinstance(raw, dict):
34
+ return raw
35
+ if isinstance(raw, str):
36
+ try:
37
+ parsed = json.loads(raw)
38
+ if isinstance(parsed, dict):
39
+ return parsed
40
+ except (ValueError, TypeError):
41
+ pass
42
+ return None
31
43
 
32
44
 
33
45
  def main():
34
46
  try:
35
- input_data = json.load(sys.stdin)
47
+ payload = json.load(sys.stdin)
36
48
  except (ValueError, EOFError, OSError):
37
49
  sys.exit(0)
38
50
 
39
- tool_input = input_data.get("tool_input")
40
- if not isinstance(tool_input, dict):
51
+ tool_name = _safe_string(payload.get("tool_name", "")).strip()
52
+ if tool_name != "Bash":
41
53
  sys.exit(0)
42
54
 
43
- file_path = tool_input.get("file_path", "")
44
- if not isinstance(file_path, str) or not file_path:
45
- sys.exit(0)
55
+ # Extract command and response
56
+ tool_input = payload.get("tool_input") if isinstance(payload.get("tool_input"), dict) else {}
57
+ command = _safe_string(tool_input.get("command", "")).strip()
46
58
 
47
- if not file_path.endswith(".md"):
48
- sys.exit(0)
59
+ raw_response = payload.get("tool_response")
60
+ parsed = _parse_tool_response(raw_response)
49
61
 
50
- normalized = file_path.replace("\\", "/")
51
- basename = os.path.basename(normalized)
62
+ exit_code = None
63
+ stdout_text = ""
64
+ stderr_text = ""
52
65
 
53
- # Skip non-vault files
54
- skip_names = {"README.md", "CHANGELOG.md", "CONTRIBUTING.md", "CLAUDE.md", "AGENTS.md", "LICENSE"}
55
- if basename in skip_names:
56
- sys.exit(0)
57
- if basename.startswith("README.") and basename.endswith(".md"):
58
- sys.exit(0)
66
+ if parsed:
67
+ exit_code = _safe_int(parsed.get("exit_code")) or _safe_int(parsed.get("exitCode"))
68
+ stdout_text = _safe_string(parsed.get("stdout", "")).strip()
69
+ stderr_text = _safe_string(parsed.get("stderr", "")).strip()
70
+ else:
71
+ stdout_text = _safe_string(raw_response).strip()
59
72
 
60
- skip_paths = [".claude/", ".codex/", ".codex-vault/", ".mind/", "templates/", "thinking/", "node_modules/", "plugin/", "docs/"]
61
- if any(skip in normalized for skip in skip_paths):
73
+ combined = f"{stderr_text}\n{stdout_text}".strip()
74
+ if not combined:
62
75
  sys.exit(0)
63
76
 
64
- warnings = []
65
-
66
- try:
67
- content = Path(file_path).read_text(encoding="utf-8")
68
-
69
- if not content.startswith("---"):
70
- warnings.append("Missing YAML frontmatter")
71
- else:
72
- parts = content.split("---", 2)
73
- if len(parts) >= 3:
74
- fm = parts[1]
75
- if "date:" not in fm and basename != "log.md":
76
- warnings.append("Missing `date` in frontmatter")
77
- if "tags:" not in fm:
78
- warnings.append("Missing `tags` in frontmatter")
79
- if "description:" not in fm:
80
- warnings.append("Missing `description` in frontmatter (~150 chars)")
81
-
82
- if len(content) > 300 and "[[" not in content:
83
- warnings.append("No [[wikilinks]] found — every note should link to at least one other note")
84
-
85
- # Check for unfilled template placeholders
86
- placeholders = re.findall(r"\{\{[^}]+\}\}", content)
87
- if placeholders:
88
- examples = ", ".join(placeholders[:3])
89
- warnings.append(f"Unfilled template placeholders found: {examples}")
90
-
91
- # Validate log.md format
92
- if basename == "log.md":
93
- log_warnings = _check_log_format(content)
94
- warnings.extend(log_warnings)
95
-
96
- except Exception:
77
+ # Check for hard failures
78
+ if HARD_FAILURE_PATTERNS.search(combined):
79
+ output = {
80
+ "decision": "block",
81
+ "reason": "Bash output indicates a command/setup failure that should be fixed before retrying.",
82
+ "hookSpecificOutput": {
83
+ "hookEventName": "PostToolUse",
84
+ "additionalContext": (
85
+ "Bash reported `command not found`, `permission denied`, or a missing file/path. "
86
+ "Verify the command, dependency installation, PATH, file permissions, "
87
+ "and referenced paths before retrying."
88
+ ),
89
+ },
90
+ }
91
+ sys.stdout.write(json.dumps(output) + "\n")
92
+ sys.stdout.flush()
97
93
  sys.exit(0)
98
94
 
99
- if warnings:
100
- hint_list = "\n".join(f" - {w}" for w in warnings)
101
- count = len(warnings)
102
- first = warnings[0]
103
- if count == 1:
104
- feedback = f"\u26a0\ufe0f vault: {basename} — {first}"
105
- else:
106
- feedback = f"\u26a0\ufe0f vault: {basename} — {first} (+{count - 1} more)"
107
-
108
- # Codex CLI: use stderr for feedback (no systemMessage rendering)
109
- sys.stderr.write(f" {feedback}\n")
110
-
95
+ # Check for non-zero exit code with informative output
96
+ if exit_code is not None and exit_code != 0 and len(combined) > 0:
111
97
  output = {
98
+ "decision": "block",
99
+ "reason": "Bash command returned a non-zero exit code but produced useful output that should be reviewed before retrying.",
112
100
  "hookSpecificOutput": {
113
101
  "hookEventName": "PostToolUse",
114
- "additionalContext": f"Vault warnings for `{basename}`:\n{hint_list}\nFix these before moving on."
102
+ "additionalContext": (
103
+ "The Bash output appears informative despite the non-zero exit code. "
104
+ "Review and report the output before retrying instead of assuming the command simply failed."
105
+ ),
115
106
  },
116
107
  }
117
- json.dump(output, sys.stdout)
108
+ sys.stdout.write(json.dumps(output) + "\n")
118
109
  sys.stdout.flush()
110
+ sys.exit(0)
119
111
 
120
112
  sys.exit(0)
121
113
 
@@ -283,16 +283,12 @@ def main():
283
283
  icon = "\U0001f504" if mode == "auto" else "\U0001f4a1"
284
284
  label = ", ".join(feedback_parts) if feedback_parts else "intent detected"
285
285
 
286
- # Codex CLI: use stderr for feedback (no systemMessage rendering)
287
- sys.stderr.write(f" {icon} vault: {label}\n")
288
-
289
- output = {
290
- "hookSpecificOutput": {
291
- "hookEventName": "UserPromptSubmit",
292
- "additionalContext": context
293
- },
294
- }
295
- json.dump(output, sys.stdout)
286
+ # stdout print directly displayed to user
287
+ print(f" {icon} vault: {label}")
288
+
289
+ # systemMessage → injected into LLM context
290
+ result = {"systemMessage": context}
291
+ sys.stdout.write(json.dumps(result))
296
292
  sys.stdout.flush()
297
293
 
298
294
  sys.exit(0)
@@ -345,13 +345,9 @@ def _build_banner(vault_dir):
345
345
  def main():
346
346
  vault_dir = _find_vault_root()
347
347
  if not vault_dir:
348
- output = {
349
- "hookSpecificOutput": {
350
- "hookEventName": "SessionStart",
351
- "additionalContext": "## Session Context\n\n(No vault found)"
352
- }
353
- }
354
- json.dump(output, sys.stdout)
348
+ print("📚 Codex-Vault: No vault found")
349
+ result = {"systemMessage": "## Session Context\n\n(No vault found)"}
350
+ sys.stdout.write(json.dumps(result))
355
351
  sys.exit(0)
356
352
 
357
353
  # Read hook input for session metadata
@@ -361,20 +357,21 @@ def main():
361
357
  event = {}
362
358
 
363
359
  context = _build_context(vault_dir)
364
- banner = _build_banner(vault_dir)
365
360
 
366
- # Codex CLI: systemMessage not rendered by TUI,
367
- # use stderr for terminal visibility (best effort)
368
- sys.stderr.write(banner + "\n")
361
+ # stdout print directly displayed to user
362
+ goal = _north_star_goal(vault_dir)
363
+ active_dir = os.path.join(vault_dir, "work", "active")
364
+ work_count = len([f for f in os.listdir(active_dir) if f.endswith(".md")]) if os.path.isdir(active_dir) else 0
365
+ changes = _git_status_short(vault_dir)
369
366
 
370
- output = {
371
- "hookSpecificOutput": {
372
- "hookEventName": "SessionStart",
373
- "additionalContext": context
374
- },
375
- }
367
+ print("📚 Codex-Vault loaded!")
368
+ if goal:
369
+ print(f"🎯 {goal}")
370
+ print(f"📝 Active: {work_count} | Changes: {len(changes)}")
376
371
 
377
- json.dump(output, sys.stdout)
372
+ # systemMessage → injected into LLM context
373
+ result = {"systemMessage": context}
374
+ sys.stdout.write(json.dumps(result))
378
375
  sys.stdout.flush()
379
376
  sys.exit(0)
380
377
 
package/vault/AGENTS.md CHANGED
@@ -1,118 +1 @@
1
- # Codex-Vault — Core Instructions
2
-
3
- A structured knowledge vault maintained by an LLM agent. You write notes, maintain links, and keep indexes current. The human curates sources, directs analysis, and asks questions.
4
-
5
- ## Vault Structure
6
-
7
- | Folder | Purpose |
8
- |--------|---------|
9
- | `Home.md` | Vault entry point — quick links, current focus |
10
- | `brain/` | Persistent memory — goals, decisions, patterns |
11
- | `work/` | Work notes index (`Index.md`) |
12
- | `work/active/` | Current projects (move to archive when done) |
13
- | `work/archive/` | Completed work |
14
- | `templates/` | Note templates with YAML frontmatter |
15
- | `sources/` | Raw source documents — immutable, LLM reads only |
16
- | `thinking/` | Scratchpad — promote findings, then delete |
17
- | `reference/` | Saved answers and analyses from query writeback |
18
-
19
- ## Session Lifecycle
20
-
21
- ### Start
22
-
23
- The SessionStart hook injects: North Star goals, recent git changes, active work, vault file listing. You start with context, not a blank slate.
24
-
25
- ### Work
26
-
27
- 1. The classify hook detects intent and suggests skills — **do not auto-execute**. Suggest the skill to the user and let them decide.
28
- 2. Available skills: `/dump`, `/recall`, `/ingest`, `/wrap-up`
29
- 3. Search before creating — check if a related note exists (use `/recall <topic>` for targeted vault search)
30
- 4. Update `work/Index.md` if a new note was created
31
-
32
- ### End
33
-
34
- When the user says "wrap up" or similar:
35
- 1. Verify new notes have frontmatter and wikilinks
36
- 2. Update `work/Index.md` with any new or completed notes
37
- 3. Archive completed projects: move from `work/active/` to `work/archive/`
38
- 4. Check if `brain/` notes need updating with new decisions or patterns
39
-
40
- ## Creating Notes
41
-
42
- 1. **Always use YAML frontmatter**: `date`, `description` (~150 chars), `tags`
43
- 2. **Use templates** from `templates/`
44
- 3. **Place files correctly**: active work in `work/active/`, completed in `work/archive/`, source summaries in `work/active/` (tag: `source-summary`), drafts in `thinking/`
45
- 4. **Name files descriptively** — use the note title as filename
46
-
47
- ## Linking — Critical
48
-
49
- **Graph-first.** Folders group by purpose, links group by meaning. A note lives in one folder but links to many notes.
50
-
51
- **A note without links is a bug.** Every new note must link to at least one existing note via `[[wikilinks]]`.
52
-
53
- Link syntax:
54
- - `[[Note Title]]` — standard wikilink
55
- - `[[Note Title|display text]]` — aliased
56
- - `[[Note Title#Heading]]` — deep link
57
-
58
- ### When to Link
59
-
60
- - Work note ↔ Decision Record (bidirectional)
61
- - Index → all work notes
62
- - North Star → active projects
63
- - Memories → source notes
64
-
65
- ## Memory System
66
-
67
- All persistent memory lives in `brain/`:
68
-
69
- | File | Stores |
70
- |------|--------|
71
- | `North Star.md` | Goals and focus areas — read every session |
72
- | `Memories.md` | Index of memory topics |
73
- | `Key Decisions.md` | Decisions worth recalling across sessions |
74
- | `Patterns.md` | Recurring patterns discovered across work |
75
-
76
- When asked to "remember" something: write to the appropriate `brain/` file with a wikilink to context.
77
-
78
- ## Sources & Ingest
79
-
80
- `sources/` holds raw source documents (articles, papers, web clips). This is the immutable layer — the agent reads from it but never modifies source files.
81
-
82
- - Drop raw files into `sources/` (markdown preferred) or use `/ingest` with a URL
83
- - `/ingest` reads the source, discusses key takeaways, then creates a **Source Summary** in `work/active/` with tag `source-summary`
84
- - The summary uses the Source Summary template: Key Takeaways, Summary, Connections, Quotes/Data Points
85
- - Every ingest updates `work/Index.md` (Sources section) and checks for cross-links to existing notes
86
- - If the source contains decisions or patterns, update the relevant `brain/` notes too
87
- - Source summaries link back to the raw source via the `source` frontmatter field
88
-
89
- ## Operation Log
90
-
91
- Append to `log.md` after significant operations: ingests, decisions, project archives, maintenance passes.
92
-
93
- - Format: `## [YYYY-MM-DD] <type> | <title>` followed by bullet points
94
- - Types: `ingest`, `session`, `query`, `maintenance`, `decision`, `archive`
95
- - Don't log every small edit — only operations that change the vault's knowledge state
96
- - Entries are append-only; never edit or delete previous entries
97
-
98
- ## Query Writeback
99
-
100
- When answering a substantial question that synthesizes multiple vault notes:
101
-
102
- 1. Offer: "This answer could be useful later — want me to save it as a reference note?"
103
- 2. If yes, create a Reference Note in `reference/` using the template
104
- 3. Link the reference note from related work notes in `## Related`
105
- 4. Add the reference note to `work/Index.md` under `## Reference`
106
- 5. Don't prompt for trivial questions — only for answers that synthesize, compare, or analyze
107
-
108
- ## Vault Location
109
-
110
- The vault may live at the project root or in a `vault/` subdirectory. Use the SessionStart context to determine the actual path. All folder references above (e.g. `brain/`, `work/active/`) are relative to the vault root.
111
-
112
- ## Rules
113
-
114
- - Preserve existing frontmatter when editing notes
115
- - Always check for and suggest connections between notes
116
- - Every note must have a `description` field (~150 chars)
117
- - When reorganizing, never delete without user confirmation
118
- - Use `[[wikilinks]]` not markdown links
1
+ @CLAUDE.md
@@ -1,221 +0,0 @@
1
- #!/bin/bash
2
- set -eo pipefail
3
-
4
- # Codex-Vault session-start hook
5
- # Injects vault context into the agent's prompt at session start.
6
- # Works with any agent that supports SessionStart hooks (Claude Code, Codex CLI).
7
- #
8
- # Output contract (matches claude-mem pattern):
9
- # stdout → JSON with hookSpecificOutput.additionalContext (agent context)
10
- # stderr → visible banner (user terminal)
11
- #
12
- # Dynamic context: adapts git log window, reads full North Star,
13
- # shows all active work, and includes uncommitted changes.
14
-
15
- VAULT_DIR="${CLAUDE_PROJECT_DIR:-${CODEX_PROJECT_DIR:-$(pwd)}}"
16
- cd "$VAULT_DIR"
17
-
18
- # Find vault root (look for Home.md or brain/ directory)
19
- if [ ! -f "Home.md" ] && [ ! -d "brain/" ]; then
20
- # Try vault/ subdirectory (integrated layout)
21
- if [ -d "vault/" ]; then
22
- VAULT_DIR="$VAULT_DIR/vault"
23
- cd "$VAULT_DIR"
24
- fi
25
- fi
26
-
27
- # --- Visible banner (stderr → user terminal) ---
28
- {
29
- echo ""
30
- echo " 📚 Codex-Vault · Session Loaded"
31
-
32
- # North Star preview
33
- if [ -f "brain/North Star.md" ]; then
34
- GOAL=$(sed -n '/^## Current Focus/,/^## /{/^- ./{s/^- //;p;q;};}' "brain/North Star.md" | cut -c1-40)
35
- [ -n "$GOAL" ] && echo " 🎯 $GOAL" || echo " 🎯 (set goals in North Star.md)"
36
- else
37
- echo " 🎯 (create brain/North Star.md)"
38
- fi
39
-
40
- # Active work count
41
- WORK_COUNT=$(find work/active -name "*.md" 2>/dev/null | wc -l | tr -d ' ')
42
- echo " 📋 $WORK_COUNT active project(s)"
43
-
44
- # Uncommitted changes count
45
- CHANGE_COUNT=$(git status --short -- . 2>/dev/null | wc -l | tr -d ' ')
46
- if [ "$CHANGE_COUNT" -gt 0 ]; then
47
- echo " ✏️ $CHANGE_COUNT uncommitted change(s)"
48
- else
49
- echo " ✅ working tree clean"
50
- fi
51
- echo ""
52
- } >&2
53
-
54
- # --- Collect context into variable ---
55
- CONTEXT=$(
56
- cat <<'CONTEXT_HEADER'
57
- ## Session Context
58
-
59
- CONTEXT_HEADER
60
-
61
- echo "### Date"
62
- echo "$(date +%Y-%m-%d) ($(date +%A))"
63
- echo ""
64
-
65
- # North Star — full file (should be concise by design)
66
- echo "### North Star"
67
- if [ -f "brain/North Star.md" ]; then
68
- cat "brain/North Star.md"
69
- else
70
- echo "(No North Star found — create brain/North Star.md to set goals)"
71
- fi
72
- echo ""
73
-
74
- # Recent changes — adaptive window
75
- echo "### Recent Changes"
76
- COMMITS_48H=$(git log --oneline --since="48 hours ago" --no-merges 2>/dev/null | wc -l | tr -d ' ')
77
- if [ "$COMMITS_48H" -gt 0 ]; then
78
- echo "(last 48 hours)"
79
- git log --oneline --since="48 hours ago" --no-merges 2>/dev/null | head -15 || true
80
- else
81
- COMMITS_7D=$(git log --oneline --since="7 days ago" --no-merges 2>/dev/null | wc -l | tr -d ' ')
82
- if [ "$COMMITS_7D" -gt 0 ]; then
83
- echo "(nothing in 48h — showing last 7 days)"
84
- git log --oneline --since="7 days ago" --no-merges 2>/dev/null | head -15 || true
85
- else
86
- echo "(nothing recent — showing last 5 commits)"
87
- git log --oneline -5 --no-merges 2>/dev/null || echo "(no git history)"
88
- fi
89
- fi
90
- echo ""
91
-
92
- # Recent operations from log — adaptive
93
- echo "### Recent Operations"
94
- if [ -f "log.md" ]; then
95
- ENTRY_COUNT=$(grep -c "^## \[" "log.md" 2>/dev/null || echo "0")
96
- if [ "$ENTRY_COUNT" -gt 0 ]; then
97
- # Show last 5 entries with full header line (includes date + type)
98
- grep "^## \[" "log.md" | tail -5
99
- else
100
- echo "(no entries in log.md)"
101
- fi
102
- else
103
- echo "(no log.md)"
104
- fi
105
- echo ""
106
-
107
- # Active work — show all (this is the current focus, no truncation)
108
- echo "### Active Work"
109
- if [ -d "work/active" ]; then
110
- WORK_FILES=$(ls work/active/*.md 2>/dev/null || true)
111
- if [ -n "$WORK_FILES" ]; then
112
- echo "$WORK_FILES" | sed 's|work/active/||;s|\.md$||'
113
- else
114
- echo "(none)"
115
- fi
116
- else
117
- echo "(no work/active/ directory)"
118
- fi
119
- echo ""
120
-
121
- # Uncommitted changes — shows agent what's in-flight
122
- echo "### Uncommitted Changes"
123
- CHANGES=$(git status --short -- . 2>/dev/null | head -20 || true)
124
- if [ -n "$CHANGES" ]; then
125
- echo "$CHANGES"
126
- else
127
- echo "(working tree clean)"
128
- fi
129
- echo ""
130
-
131
- # Recently modified brain files — highlights memory that may need review
132
- echo "### Recently Modified Brain Files"
133
- if [ -d "brain" ]; then
134
- GIT_DIR=$(git rev-parse --git-dir 2>/dev/null || echo "")
135
- if [ -n "$GIT_DIR" ] && [ -f "$GIT_DIR/index" ]; then
136
- RECENT_BRAIN=$(find brain/ -name "*.md" -newer "$GIT_DIR/index" 2>/dev/null || true)
137
- else
138
- RECENT_BRAIN=""
139
- fi
140
- if [ -n "$RECENT_BRAIN" ]; then
141
- echo "$RECENT_BRAIN" | sed 's|brain/||;s|\.md$||'
142
- else
143
- # Fallback: show brain files modified in last 7 days
144
- RECENT_BRAIN=$(find brain/ -name "*.md" -mtime -7 2>/dev/null || true)
145
- if [ -n "$RECENT_BRAIN" ]; then
146
- echo "(modified in last 7 days)"
147
- echo "$RECENT_BRAIN" | sed 's|brain/||;s|\.md$||'
148
- else
149
- echo "(no recent changes)"
150
- fi
151
- fi
152
- fi
153
- echo ""
154
-
155
- # Vault file listing — tiered to avoid flooding context in large vaults
156
- echo "### Vault Files"
157
- ALL_FILES=$(find . -name "*.md" -not -path "./.git/*" -not -path "./.obsidian/*" -not -path "./thinking/*" -not -path "./.claude/*" -not -path "./.codex/*" -not -path "./.codex-vault/*" -not -path "./node_modules/*" 2>/dev/null | sort)
158
- FILE_COUNT=$(echo "$ALL_FILES" | grep -c . 2>/dev/null || echo "0")
159
-
160
- _folder_summary() {
161
- echo "$ALL_FILES" | sed 's|^\./||' | cut -d/ -f1 | sort | uniq -c | sort -rn | while read count dir; do
162
- echo " $dir/ ($count files)"
163
- done
164
- }
165
-
166
- _key_files() {
167
- echo "$ALL_FILES" | grep -E "(Home|Index|North Star|Memories|Key Decisions|Patterns|log)\\.md$" || true
168
- }
169
-
170
- if [ "$FILE_COUNT" -le 20 ]; then
171
- echo "$ALL_FILES"
172
-
173
- elif [ "$FILE_COUNT" -le 50 ]; then
174
- HOT_FILES=$(echo "$ALL_FILES" | grep -v -E "^\./sources/|^\./work/archive/" || true)
175
- COLD_COUNT=$(echo "$ALL_FILES" | grep -E "^\./sources/|^\./work/archive/" | grep -c . 2>/dev/null || echo "0")
176
-
177
- if [ -n "$HOT_FILES" ]; then
178
- echo "$HOT_FILES"
179
- fi
180
- if [ "$COLD_COUNT" -gt 0 ]; then
181
- echo ""
182
- echo "(+ $COLD_COUNT files in sources/ and work/archive/ — use /recall to search)"
183
- fi
184
-
185
- elif [ "$FILE_COUNT" -le 150 ]; then
186
- echo "($FILE_COUNT files — showing summary)"
187
- echo ""
188
- _folder_summary
189
- echo ""
190
- echo "Recently modified (7 days):"
191
- find . -name "*.md" -mtime -7 -not -path "./.git/*" -not -path "./.obsidian/*" -not -path "./thinking/*" -not -path "./.claude/*" -not -path "./.codex/*" -not -path "./.codex-vault/*" -not -path "./node_modules/*" 2>/dev/null | sort || echo " (none)"
192
- echo ""
193
- echo "Key files:"
194
- _key_files
195
-
196
- else
197
- echo "($FILE_COUNT files — showing summary)"
198
- echo ""
199
- _folder_summary
200
- echo ""
201
- echo "Recently modified (3 days):"
202
- find . -name "*.md" -mtime -3 -not -path "./.git/*" -not -path "./.obsidian/*" -not -path "./thinking/*" -not -path "./.claude/*" -not -path "./.codex/*" -not -path "./.codex-vault/*" -not -path "./node_modules/*" 2>/dev/null | sort || echo " (none)"
203
- echo ""
204
- echo "Key files:"
205
- _key_files
206
- echo ""
207
- echo "Use /recall <topic> to search the vault."
208
- fi
209
- )
210
-
211
- # --- Output structured JSON (stdout → agent via hookSpecificOutput) ---
212
- python3 -c "
213
- import json, sys
214
- context = sys.stdin.read()
215
- json.dump({
216
- 'hookSpecificOutput': {
217
- 'hookEventName': 'SessionStart',
218
- 'additionalContext': context
219
- }
220
- }, sys.stdout)
221
- " <<< "$CONTEXT"