@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 +15 -7
- package/README.zh-CN.md +15 -7
- package/package.json +6 -1
- package/plugin/hooks/codex/classify-message.py +3 -5
- package/plugin/hooks/codex/session-start.py +20 -7
- package/plugin/hooks/codex/validate-write.py +79 -87
- package/vault/thinking/.gitkeep +0 -0
- package/plugin/hooks/session-start.sh +0 -221
- package/vault/.claude/settings.json +0 -39
- package/vault/.claude/skills/dump/SKILL.md +0 -29
- package/vault/.claude/skills/ingest/SKILL.md +0 -63
- package/vault/.claude/skills/recall/SKILL.md +0 -54
- package/vault/.claude/skills/wrap-up/SKILL.md +0 -35
- package/vault/.codex/config.toml +0 -2
- package/vault/.codex/hooks.json +0 -28
- package/vault/.codex/skills/dump/SKILL.md +0 -29
- package/vault/.codex/skills/ingest/SKILL.md +0 -63
- package/vault/.codex/skills/recall/SKILL.md +0 -54
- package/vault/.codex/skills/wrap-up/SKILL.md +0 -35
- package/vault/.codex-vault/hooks/claude/classify-message.py +0 -305
- package/vault/.codex-vault/hooks/claude/session-start.py +0 -383
- package/vault/.codex-vault/hooks/claude/validate-write.py +0 -123
- package/vault/.codex-vault/hooks/codex/classify-message.py +0 -305
- package/vault/.codex-vault/hooks/codex/session-start.py +0 -387
- package/vault/.codex-vault/hooks/codex/validate-write.py +0 -127
- package/vault/AGENTS.md +0 -118
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
|
-
|
|
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 |
|
|
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
|
-
|
|
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`
|
|
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.
|
|
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
|
-
#
|
|
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.
|
|
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.
|
|
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
|
-
#
|
|
367
|
-
|
|
368
|
-
|
|
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
|
-
"""
|
|
2
|
+
"""PostToolUse hook for Codex CLI — Bash command validation.
|
|
3
3
|
|
|
4
|
-
Checks
|
|
5
|
-
|
|
6
|
-
|
|
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
|
-
|
|
20
|
-
"
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
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
|
-
|
|
47
|
+
payload = json.load(sys.stdin)
|
|
36
48
|
except (ValueError, EOFError, OSError):
|
|
37
49
|
sys.exit(0)
|
|
38
50
|
|
|
39
|
-
|
|
40
|
-
if
|
|
51
|
+
tool_name = _safe_string(payload.get("tool_name", "")).strip()
|
|
52
|
+
if tool_name != "Bash":
|
|
41
53
|
sys.exit(0)
|
|
42
54
|
|
|
43
|
-
|
|
44
|
-
if
|
|
45
|
-
|
|
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
|
-
|
|
48
|
-
|
|
59
|
+
raw_response = payload.get("tool_response")
|
|
60
|
+
parsed = _parse_tool_response(raw_response)
|
|
49
61
|
|
|
50
|
-
|
|
51
|
-
|
|
62
|
+
exit_code = None
|
|
63
|
+
stdout_text = ""
|
|
64
|
+
stderr_text = ""
|
|
52
65
|
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
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
|
-
|
|
61
|
-
if
|
|
73
|
+
combined = f"{stderr_text}\n{stdout_text}".strip()
|
|
74
|
+
if not combined:
|
|
62
75
|
sys.exit(0)
|
|
63
76
|
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
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
|
-
|
|
100
|
-
|
|
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":
|
|
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.
|
|
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
|
-
}
|