@suwujs/codex-vault 0.6.0 → 0.7.1

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
@@ -174,6 +174,14 @@ npx @suwujs/codex-vault upgrade # Upgrade hooks and skills (preserves vault
174
174
  npx @suwujs/codex-vault uninstall # Remove hooks and skills (preserves vault data)
175
175
  ```
176
176
 
177
+ ## Testing
178
+
179
+ ```bash
180
+ npm test # Full E2E test suite
181
+ npm run test:cli # CLI commands only (22 tests)
182
+ npm run test:hooks # Hook scripts only (33 tests)
183
+ ```
184
+
177
185
  ## Requirements
178
186
 
179
187
  - Git
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 结构
@@ -154,6 +154,14 @@ npx @suwujs/codex-vault upgrade # 升级 hooks 和 skills(保留 vault 数
154
154
  npx @suwujs/codex-vault uninstall # 移除 hooks 和 skills(保留 vault 数据)
155
155
  ```
156
156
 
157
+ ## 测试
158
+
159
+ ```bash
160
+ npm test # 完整 E2E 测试
161
+ npm run test:cli # CLI 命令测试(22 个)
162
+ npm run test:hooks # Hook 脚本测试(33 个)
163
+ ```
164
+
157
165
  ## 配置
158
166
 
159
167
  | 配置项 | 文件 | 说明 |
package/package.json CHANGED
@@ -1,12 +1,17 @@
1
1
  {
2
2
  "name": "@suwujs/codex-vault",
3
- "version": "0.6.0",
3
+ "version": "0.7.1",
4
4
  "description": "Persistent knowledge vault for LLM agents (Claude Code, Codex CLI)",
5
5
  "license": "MIT",
6
6
  "repository": {
7
7
  "type": "git",
8
8
  "url": "https://github.com/sukbearai/codex-vault"
9
9
  },
10
+ "scripts": {
11
+ "test": "bash tests/test_e2e.sh",
12
+ "test:cli": "bash tests/test_cli.sh",
13
+ "test:hooks": "bash tests/test_hooks.sh"
14
+ },
10
15
  "bin": {
11
16
  "codex-vault": "bin/cli.js"
12
17
  },
@@ -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
 
File without changes
@@ -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"
@@ -1,39 +0,0 @@
1
- {
2
- "hooks": {
3
- "SessionStart": [
4
- {
5
- "matcher": "startup|resume|compact",
6
- "hooks": [
7
- {
8
- "type": "command",
9
- "command": "python3 .codex-vault/hooks/claude/session-start.py",
10
- "timeout": 30
11
- }
12
- ]
13
- }
14
- ],
15
- "UserPromptSubmit": [
16
- {
17
- "hooks": [
18
- {
19
- "type": "command",
20
- "command": "python3 .codex-vault/hooks/claude/classify-message.py",
21
- "timeout": 15
22
- }
23
- ]
24
- }
25
- ],
26
- "PostToolUse": [
27
- {
28
- "matcher": "Write|Edit",
29
- "hooks": [
30
- {
31
- "type": "command",
32
- "command": "python3 .codex-vault/hooks/claude/validate-write.py",
33
- "timeout": 15
34
- }
35
- ]
36
- }
37
- ]
38
- }
39
- }