codebase-wiki 0.1.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/CHANGELOG.md +34 -0
- package/LICENSE +17 -0
- package/README.md +130 -0
- package/dist/CHANGELOG.md +34 -0
- package/dist/LICENSE +17 -0
- package/dist/README.md +130 -0
- package/dist/chunks/build.mjs +1976 -0
- package/dist/chunks/config.mjs +174 -0
- package/dist/chunks/llm.mjs +214 -0
- package/dist/code-wiki-mcp.mjs +367 -0
- package/dist/code-wiki.mjs +11 -0
- package/dist/codewiki.mjs +2002 -0
- package/dist/grammars/c.wasm +0 -0
- package/dist/grammars/cpp.wasm +0 -0
- package/dist/grammars/go.wasm +0 -0
- package/dist/grammars/java.wasm +0 -0
- package/dist/grammars/javascript.wasm +0 -0
- package/dist/grammars/python.wasm +0 -0
- package/dist/grammars/ruby.wasm +0 -0
- package/dist/grammars/rust.wasm +0 -0
- package/dist/grammars/tsx.wasm +0 -0
- package/dist/grammars/typescript.wasm +0 -0
- package/dist/plugin/.mcp.json +11 -0
- package/dist/plugin/CLAUDE.md +28 -0
- package/dist/plugin/commands/wiki-architecture.md +10 -0
- package/dist/plugin/commands/wiki-build.md +23 -0
- package/dist/plugin/commands/wiki-enrich.md +22 -0
- package/dist/plugin/commands/wiki-graph.md +16 -0
- package/dist/plugin/commands/wiki-help.md +49 -0
- package/dist/plugin/commands/wiki-refresh.md +13 -0
- package/dist/plugin/commands/wiki-search.md +18 -0
- package/dist/plugin/commands/wiki-symbol.md +21 -0
- package/dist/plugin/hooks/hooks.json +74 -0
- package/dist/plugin/hooks/nudge.mjs +197 -0
- package/dist/plugin/hooks/post-read-reminder.mjs +56 -0
- package/dist/plugin/hooks/post-tool-use.mjs +60 -0
- package/dist/plugin/hooks/pre-compact.mjs +25 -0
- package/dist/plugin/hooks/session-start.mjs +38 -0
- package/dist/plugin/hooks/user-prompt-submit.mjs +28 -0
- package/dist/plugin/manifest.json +54 -0
- package/package.json +100 -0
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
# Working with `.codewiki/`
|
|
2
|
+
|
|
3
|
+
If you see a `.codewiki/` directory in this project, the `code-wiki` Claude Code
|
|
4
|
+
plugin is enabled. **Prefer reading `.codewiki/` pages over reading source files
|
|
5
|
+
directly.** The wiki pages are short, structured, and the model is auto-nudged
|
|
6
|
+
toward them whenever you call `Read | Grep | Glob | cat | head | tail`.
|
|
7
|
+
|
|
8
|
+
## Workflow
|
|
9
|
+
|
|
10
|
+
1. On a new project or after a major refactor, suggest `/wiki-build` first.
|
|
11
|
+
2. To find a symbol: `/wiki-search <query>` or the `wiki_search` MCP tool.
|
|
12
|
+
3. To drill into a single symbol or file: `/wiki-symbol <path>:<name>` or `wiki_drill`.
|
|
13
|
+
4. To understand callers/callees: `/wiki-graph <symbol>`.
|
|
14
|
+
5. After making non-trivial edits, suggest `/wiki-refresh` (the PostToolUse hook
|
|
15
|
+
already marks changed files; refresh re-renders them).
|
|
16
|
+
|
|
17
|
+
## Hard rules
|
|
18
|
+
|
|
19
|
+
- **Never** write into `.codewiki/` directly. Always run `codewiki build` /
|
|
20
|
+
`codewiki refresh`. The plugin permissions deny those writes for a reason.
|
|
21
|
+
- **Never** cat/head/tail a source file when its `.codewiki/files/...md` exists.
|
|
22
|
+
- After a long session, the `PreCompact` hook may have marked `.codewiki/` as
|
|
23
|
+
possibly stale. Run `/wiki-refresh` proactively.
|
|
24
|
+
|
|
25
|
+
## Disabling
|
|
26
|
+
|
|
27
|
+
Set `nudge.enabled: false` in `.codewikirc` (or `package.json#code-wiki.nudge.enabled`)
|
|
28
|
+
to silence auto-nudges. The wiki itself stays usable.
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
---
|
|
2
|
+
description: Show the project's architecture.md wiki page (system overview + Mermaid diagram).
|
|
3
|
+
allowed-tools: Read
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# /wiki-architecture
|
|
7
|
+
|
|
8
|
+
Read `.codewiki/architecture.md` in `${workspaceFolder}` and summarise the
|
|
9
|
+
project in 4-6 sentences. Include the Mermaid diagram verbatim (don't reformat it).
|
|
10
|
+
Then list the modules in a compact table.
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
---
|
|
2
|
+
description: Build or rebuild the codewiki for this repo. Run once on first use, or after a major refactor.
|
|
3
|
+
allowed-tools: Bash(node ${CLAUDE_PLUGIN_ROOT}/dist/codewiki.mjs build:*), Bash(node ${CLAUDE_PLUGIN_ROOT}/dist/codewiki.mjs build), Read
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# /wiki-build
|
|
7
|
+
|
|
8
|
+
Run `node ${CLAUDE_PLUGIN_ROOT}/dist/codewiki.mjs build` in `${workspaceFolder}`.
|
|
9
|
+
|
|
10
|
+
The plugin ships the CLI binary at `${CLAUDE_PLUGIN_ROOT}/dist/codewiki.mjs`, so
|
|
11
|
+
no global npm install is required. If `.codewiki/` already exists, this runs
|
|
12
|
+
a smart rebuild (only re-parses files changed in git diff). Pass `--full` to
|
|
13
|
+
force a complete rebuild.
|
|
14
|
+
|
|
15
|
+
After the build finishes, report:
|
|
16
|
+
|
|
17
|
+
1. The number of modules, files, and symbols indexed (from the CLI output).
|
|
18
|
+
2. The path `.codewiki/INDEX.md`.
|
|
19
|
+
3. A one-line summary of the largest module.
|
|
20
|
+
|
|
21
|
+
If `.codewiki/` already existed before this command, also note: "To refresh
|
|
22
|
+
later, run `/wiki-refresh` or simply edit files — the PostToolUse hook
|
|
23
|
+
invalidates changed files automatically."
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
---
|
|
2
|
+
description: Queue LLM enrichment of the wiki using Claude Haiku + Sonnet. Requires ANTHROPIC_API_KEY.
|
|
3
|
+
allowed-tools: Bash(node ${CLAUDE_PLUGIN_ROOT}/dist/codewiki.mjs enrich:*), Bash(node ${CLAUDE_PLUGIN_ROOT}/dist/codewiki.mjs enrich)
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# /wiki-enrich
|
|
7
|
+
|
|
8
|
+
Runs `node ${CLAUDE_PLUGIN_ROOT}/dist/codewiki.mjs enrich`. Requires
|
|
9
|
+
`ANTHROPIC_API_KEY` to be set in the environment.
|
|
10
|
+
|
|
11
|
+
Behaviour:
|
|
12
|
+
|
|
13
|
+
- File-level pages: Haiku generates "Notes" for files with cyclomatic > 15 OR
|
|
14
|
+
no docstring OR tokens > 800. Cached by content hash.
|
|
15
|
+
- Module-level pages: Sonnet regenerates the public surface summary if any of
|
|
16
|
+
its files changed. Cached.
|
|
17
|
+
|
|
18
|
+
This command runs in the background by default (the queue is concurrency-bounded
|
|
19
|
+
to 4). Progress is in `.codewiki/.summary-cache/`.
|
|
20
|
+
|
|
21
|
+
If `ANTHROPIC_API_KEY` is not set, the CLI prints a one-line warning and
|
|
22
|
+
exits cleanly — nothing to do.
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
---
|
|
2
|
+
description: Show callers and callees of a symbol.
|
|
3
|
+
allowed-tools: Bash(node ${CLAUDE_PLUGIN_ROOT}/dist/codewiki.mjs show:*), Bash(node ${CLAUDE_PLUGIN_ROOT}/dist/codewiki.mjs show), Read
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# /wiki-graph <symbol>
|
|
7
|
+
|
|
8
|
+
Arguments: $ARGUMENTS
|
|
9
|
+
|
|
10
|
+
Look up the symbol in `.codewiki/.graph.json` (or call the `wiki_callers` and
|
|
11
|
+
`wiki_callees` MCP tools with the symbol id). Print:
|
|
12
|
+
|
|
13
|
+
- Incoming edges (callers), grouped by file.
|
|
14
|
+
- Outgoing edges (callees), grouped by file.
|
|
15
|
+
|
|
16
|
+
Limit to 20 edges each direction. If more exist, say so and offer to expand.
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
---
|
|
2
|
+
description: Show available wiki commands.
|
|
3
|
+
allowed-tools:
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# /wiki-help
|
|
7
|
+
|
|
8
|
+
Print a one-screen summary:
|
|
9
|
+
|
|
10
|
+
## Slash commands
|
|
11
|
+
|
|
12
|
+
- `/wiki-build` — Build or rebuild the wiki
|
|
13
|
+
- `/wiki-refresh` — Incremental refresh
|
|
14
|
+
- `/wiki-search <query>` — Search symbols/files/modules
|
|
15
|
+
- `/wiki-symbol <path>:<name>` — Read one symbol page
|
|
16
|
+
- `/wiki-architecture` — System overview + diagram
|
|
17
|
+
- `/wiki-graph <symbol>` — Callers and callees
|
|
18
|
+
- `/wiki-enrich` — Optional LLM enrichment (API key required)
|
|
19
|
+
- `/wiki-help` — This help
|
|
20
|
+
|
|
21
|
+
## MCP tools (auto-discovered)
|
|
22
|
+
|
|
23
|
+
- `wiki_search`, `wiki_drill`, `wiki_callers`, `wiki_callees`, `wiki_changed_since`
|
|
24
|
+
|
|
25
|
+
## MCP resources
|
|
26
|
+
|
|
27
|
+
- `wiki://tree` — Top-level module index
|
|
28
|
+
- `wiki://architecture` — Architecture overview
|
|
29
|
+
- `wiki://module/<id>` — A module page
|
|
30
|
+
- `wiki://file/<path>` — A file page
|
|
31
|
+
- `wiki://symbol/<id>` — A symbol page
|
|
32
|
+
|
|
33
|
+
## Onboarding
|
|
34
|
+
|
|
35
|
+
1. Run `/wiki-build` once.
|
|
36
|
+
2. Edit files normally — the PostToolUse hook invalidates changed wiki pages.
|
|
37
|
+
3. Run `/wiki-refresh` after significant edits, or just keep working — the watcher auto-rebuilds.
|
|
38
|
+
|
|
39
|
+
## Note
|
|
40
|
+
|
|
41
|
+
The CLI is bundled inside the plugin at
|
|
42
|
+
`${CLAUDE_PLUGIN_ROOT}/dist/codewiki.mjs`. No global `npm install` is
|
|
43
|
+
required — the slash commands invoke it via `node` directly.
|
|
44
|
+
|
|
45
|
+
## Auto-nudge
|
|
46
|
+
|
|
47
|
+
After `/wiki-build`, every `Read | Grep | Glob | Bash cat/head/tail` call you
|
|
48
|
+
make will receive a one-line reminder pointing at the matching `.codewiki/`
|
|
49
|
+
page. This is the source of the token savings — lean into it.
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
---
|
|
2
|
+
description: Incrementally refresh the wiki based on git diff since the last index.
|
|
3
|
+
allowed-tools: Bash(node ${CLAUDE_PLUGIN_ROOT}/dist/codewiki.mjs refresh:*), Bash(node ${CLAUDE_PLUGIN_ROOT}/dist/codewiki.mjs refresh), Read
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# /wiki-refresh
|
|
7
|
+
|
|
8
|
+
Run `node ${CLAUDE_PLUGIN_ROOT}/dist/codewiki.mjs refresh` in `${workspaceFolder}`.
|
|
9
|
+
Report:
|
|
10
|
+
|
|
11
|
+
1. Which files were re-indexed (git diff since last build, listed by the CLI).
|
|
12
|
+
2. Which modules were marked stale.
|
|
13
|
+
3. The new token totals if `.codewiki/.meta.json` changed.
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
---
|
|
2
|
+
description: Search the wiki for symbols, files, or modules matching a query.
|
|
3
|
+
allowed-tools: Bash(node ${CLAUDE_PLUGIN_ROOT}/dist/codewiki.mjs search:*), Bash(node ${CLAUDE_PLUGIN_ROOT}/dist/codewiki.mjs search)
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# /wiki-search <query>
|
|
7
|
+
|
|
8
|
+
Arguments: $ARGUMENTS
|
|
9
|
+
|
|
10
|
+
Run `node ${CLAUDE_PLUGIN_ROOT}/dist/codewiki.mjs search "$ARGUMENTS" --limit 20`
|
|
11
|
+
in `${workspaceFolder}` and present the top results as a table with columns:
|
|
12
|
+
Kind, Name, File, Module.
|
|
13
|
+
|
|
14
|
+
After the table, suggest specific drill-in commands. For example, if a symbol
|
|
15
|
+
hit appears:
|
|
16
|
+
|
|
17
|
+
- `/wiki-symbol src/auth/login.ts:function.loginUser` to read the symbol page
|
|
18
|
+
- or call the MCP tool `wiki_drill` with the symbol id
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
---
|
|
2
|
+
description: Show the wiki page for a specific symbol or file (function, class, etc.).
|
|
3
|
+
allowed-tools: Bash(node ${CLAUDE_PLUGIN_ROOT}/dist/codewiki.mjs show:*), Bash(node ${CLAUDE_PLUGIN_ROOT}/dist/codewiki.mjs show), Read
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# /wiki-symbol <path>:<name>
|
|
7
|
+
|
|
8
|
+
Arguments: $ARGUMENTS
|
|
9
|
+
|
|
10
|
+
Examples:
|
|
11
|
+
|
|
12
|
+
- `/wiki-symbol src/auth/login.ts:function.loginUser`
|
|
13
|
+
- `/wiki-symbol src/auth/login.ts:class.AuthService`
|
|
14
|
+
- `/wiki-symbol src/auth/login.ts` (the whole file page)
|
|
15
|
+
|
|
16
|
+
Run `node ${CLAUDE_PLUGIN_ROOT}/dist/codewiki.mjs show "$ARGUMENTS"` and read
|
|
17
|
+
the result. Summarize:
|
|
18
|
+
|
|
19
|
+
1. The symbol's purpose (paraphrase the Description).
|
|
20
|
+
2. Its signature.
|
|
21
|
+
3. Its callers and callees (from `wiki_callers` and `wiki_callees` MCP tools).
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
{
|
|
2
|
+
"hooks": {
|
|
3
|
+
"SessionStart": [
|
|
4
|
+
{
|
|
5
|
+
"matcher": "*",
|
|
6
|
+
"hooks": [
|
|
7
|
+
{
|
|
8
|
+
"type": "command",
|
|
9
|
+
"command": "node ${CLAUDE_PLUGIN_ROOT}/hooks/session-start.mjs",
|
|
10
|
+
"timeout": 5000
|
|
11
|
+
}
|
|
12
|
+
]
|
|
13
|
+
}
|
|
14
|
+
],
|
|
15
|
+
"UserPromptSubmit": [
|
|
16
|
+
{
|
|
17
|
+
"matcher": "*",
|
|
18
|
+
"hooks": [
|
|
19
|
+
{
|
|
20
|
+
"type": "command",
|
|
21
|
+
"command": "node ${CLAUDE_PLUGIN_ROOT}/hooks/user-prompt-submit.mjs",
|
|
22
|
+
"timeout": 3000
|
|
23
|
+
}
|
|
24
|
+
]
|
|
25
|
+
}
|
|
26
|
+
],
|
|
27
|
+
"PreToolUse": [
|
|
28
|
+
{
|
|
29
|
+
"matcher": "Read|Grep|Glob|Bash",
|
|
30
|
+
"hooks": [
|
|
31
|
+
{
|
|
32
|
+
"type": "command",
|
|
33
|
+
"command": "node ${CLAUDE_PLUGIN_ROOT}/hooks/nudge.mjs",
|
|
34
|
+
"timeout": 3000
|
|
35
|
+
}
|
|
36
|
+
]
|
|
37
|
+
}
|
|
38
|
+
],
|
|
39
|
+
"PostToolUse": [
|
|
40
|
+
{
|
|
41
|
+
"matcher": "Write|Edit|MultiEdit",
|
|
42
|
+
"hooks": [
|
|
43
|
+
{
|
|
44
|
+
"type": "command",
|
|
45
|
+
"command": "node ${CLAUDE_PLUGIN_ROOT}/hooks/post-tool-use.mjs",
|
|
46
|
+
"timeout": 3000
|
|
47
|
+
}
|
|
48
|
+
]
|
|
49
|
+
},
|
|
50
|
+
{
|
|
51
|
+
"matcher": "Read",
|
|
52
|
+
"hooks": [
|
|
53
|
+
{
|
|
54
|
+
"type": "command",
|
|
55
|
+
"command": "node ${CLAUDE_PLUGIN_ROOT}/hooks/post-read-reminder.mjs",
|
|
56
|
+
"timeout": 3000
|
|
57
|
+
}
|
|
58
|
+
]
|
|
59
|
+
}
|
|
60
|
+
],
|
|
61
|
+
"PreCompact": [
|
|
62
|
+
{
|
|
63
|
+
"matcher": "*",
|
|
64
|
+
"hooks": [
|
|
65
|
+
{
|
|
66
|
+
"type": "command",
|
|
67
|
+
"command": "node ${CLAUDE_PLUGIN_ROOT}/hooks/pre-compact.mjs",
|
|
68
|
+
"timeout": 2000
|
|
69
|
+
}
|
|
70
|
+
]
|
|
71
|
+
}
|
|
72
|
+
]
|
|
73
|
+
}
|
|
74
|
+
}
|
|
@@ -0,0 +1,197 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
// PreToolUse nudge hook for Read | Grep | Glob | Bash.
|
|
3
|
+
//
|
|
4
|
+
// Reads stdin JSON: { tool_name, tool_input, cwd }
|
|
5
|
+
// Decides whether the call could be replaced (or preceded) with a wiki read.
|
|
6
|
+
// Emits a permissionDecisionReason the model sees as a reminder.
|
|
7
|
+
//
|
|
8
|
+
// M5 additions:
|
|
9
|
+
// - Honors .codewikirc.json: nudge.minFileLines (skip tiny files),
|
|
10
|
+
// nudge.minLinesForGlob (skip tiny globs)
|
|
11
|
+
// - Adds freshness check: if file mtime > .codewiki/.meta.json updatedAt,
|
|
12
|
+
// the wiki page is stale and we tell the model to run /wiki-refresh
|
|
13
|
+
// - Adds `wiki_changed_since` MCP tool hint on Grep (lets the model
|
|
14
|
+
// catch "is this in the changed set" without paying a Bash cost)
|
|
15
|
+
|
|
16
|
+
import { promises as fs } from 'node:fs'
|
|
17
|
+
import path from 'node:path'
|
|
18
|
+
|
|
19
|
+
const SOURCE_EXTS = new Set([
|
|
20
|
+
'.ts', '.tsx', '.mts', '.cts',
|
|
21
|
+
'.js', '.jsx', '.mjs', '.cjs',
|
|
22
|
+
'.py', '.pyi',
|
|
23
|
+
'.go', '.rs', '.java',
|
|
24
|
+
'.c', '.h', '.cpp', '.cc', '.cxx', '.hpp', '.hh',
|
|
25
|
+
'.rb',
|
|
26
|
+
])
|
|
27
|
+
|
|
28
|
+
const DEFAULT_MIN_FILE_LINES = 30
|
|
29
|
+
|
|
30
|
+
async function readStdinJson() {
|
|
31
|
+
try {
|
|
32
|
+
const buf = await new Promise((res) => {
|
|
33
|
+
let chunks = ''
|
|
34
|
+
process.stdin.setEncoding('utf8')
|
|
35
|
+
process.stdin.on('data', (c) => (chunks += c))
|
|
36
|
+
process.stdin.on('end', () => res(chunks))
|
|
37
|
+
})
|
|
38
|
+
return JSON.parse(buf || '{}')
|
|
39
|
+
} catch {
|
|
40
|
+
return {}
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
async function loadConfig(cwd) {
|
|
45
|
+
// Tolerant config reader — keeps this script self-contained (no TS imports).
|
|
46
|
+
try {
|
|
47
|
+
const cfg = JSON.parse(await fs.readFile(path.join(cwd, '.codewikirc.json'), 'utf8'))
|
|
48
|
+
return cfg
|
|
49
|
+
} catch {
|
|
50
|
+
try {
|
|
51
|
+
const pkg = JSON.parse(await fs.readFile(path.join(cwd, 'package.json'), 'utf8'))
|
|
52
|
+
if (pkg && pkg['code-wiki'] && pkg['code-wiki'].nudge) {
|
|
53
|
+
return { nudge: pkg['code-wiki'].nudge }
|
|
54
|
+
}
|
|
55
|
+
} catch {
|
|
56
|
+
/* ignore */
|
|
57
|
+
}
|
|
58
|
+
return null
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
async function readMeta(cwd) {
|
|
63
|
+
try {
|
|
64
|
+
return JSON.parse(await fs.readFile(path.join(cwd, '.codewiki', '.meta.json'), 'utf8'))
|
|
65
|
+
} catch {
|
|
66
|
+
return null
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
function repoRel(absOrRel, cwd) {
|
|
71
|
+
const abs = path.isAbsolute(absOrRel) ? absOrRel : path.join(cwd, absOrRel)
|
|
72
|
+
let rel = path.relative(cwd, abs)
|
|
73
|
+
rel = rel.split(path.sep).join('/')
|
|
74
|
+
if (rel.startsWith('./')) rel = rel.slice(2)
|
|
75
|
+
return rel
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
function isSourceFile(repoPath) {
|
|
79
|
+
const ext = path.extname(repoPath).toLowerCase()
|
|
80
|
+
return SOURCE_EXTS.has(ext)
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
function isReadOfWiki(repoPath) {
|
|
84
|
+
return repoPath.startsWith('.codewiki/') || repoPath.includes('/.codewiki/')
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
async function fileMTimeMs(absPath) {
|
|
88
|
+
try {
|
|
89
|
+
const s = await fs.stat(absPath)
|
|
90
|
+
return s.mtimeMs
|
|
91
|
+
} catch {
|
|
92
|
+
return 0
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
async function fileLineCount(absPath) {
|
|
97
|
+
try {
|
|
98
|
+
const buf = await fs.readFile(absPath, 'utf8')
|
|
99
|
+
return buf.split(/\r?\n/).length
|
|
100
|
+
} catch {
|
|
101
|
+
return 0
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
async function isStale(meta, absFilePath) {
|
|
106
|
+
if (!meta || !meta.updatedAt) return false
|
|
107
|
+
const mtime = await fileMTimeMs(absFilePath)
|
|
108
|
+
if (!mtime) return false
|
|
109
|
+
const metaMs = Date.parse(meta.updatedAt)
|
|
110
|
+
return Number.isFinite(metaMs) && mtime > metaMs
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
async function buildReadReason(repoPath, cwd, index, meta, cfg) {
|
|
114
|
+
if (isReadOfWiki(repoPath)) return null
|
|
115
|
+
if (!isSourceFile(repoPath)) return null
|
|
116
|
+
|
|
117
|
+
const filePage = `.codewiki/files/${repoPath}.md`
|
|
118
|
+
try {
|
|
119
|
+
await fs.stat(path.join(cwd, filePage))
|
|
120
|
+
} catch {
|
|
121
|
+
return null
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
const minLines = cfg?.nudge?.minFileLines ?? DEFAULT_MIN_FILE_LINES
|
|
125
|
+
const abs = path.join(cwd, repoPath)
|
|
126
|
+
const lines = await fileLineCount(abs)
|
|
127
|
+
if (lines < minLines) return null
|
|
128
|
+
|
|
129
|
+
const parts = [
|
|
130
|
+
`Tip: ${filePage} is a wiki summary for this file (~${budgetHint(index, repoPath)} tokens).`,
|
|
131
|
+
]
|
|
132
|
+
if (await isStale(meta, abs)) {
|
|
133
|
+
parts.push('This file is newer than the wiki — run /wiki-refresh first.')
|
|
134
|
+
}
|
|
135
|
+
parts.push('Read the wiki page first; only Read the source if you need details beyond it.')
|
|
136
|
+
return parts.join(' ')
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
function budgetHint(index, repoPath) {
|
|
140
|
+
const f = index?.files?.find((x) => x.path === repoPath)
|
|
141
|
+
return f?.tokens ?? 600
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
async function main() {
|
|
145
|
+
const payload = await readStdinJson()
|
|
146
|
+
const cwd = payload.cwd || process.env.CODEWIKI_CWD || process.cwd()
|
|
147
|
+
const meta = await readMeta(cwd)
|
|
148
|
+
if (!meta) return // no wiki → no nudge
|
|
149
|
+
|
|
150
|
+
const cfg = await loadConfig(cwd)
|
|
151
|
+
if (cfg?.nudge?.enabled === false) return
|
|
152
|
+
|
|
153
|
+
// Soft-delete: respect TTL on identical nudges? M5 keeps it simple — no TTL.
|
|
154
|
+
// Future: timestamp in .codewiki/.state.json.
|
|
155
|
+
|
|
156
|
+
const tool = payload.tool_name
|
|
157
|
+
const ti = payload.tool_input || {}
|
|
158
|
+
let reason = null
|
|
159
|
+
|
|
160
|
+
if (tool === 'Read' && typeof ti.file_path === 'string') {
|
|
161
|
+
const repoPath = repoRel(ti.file_path, cwd)
|
|
162
|
+
const index = payload._index || null // hook can't reach the index from here
|
|
163
|
+
reason = await buildReadReason(repoPath, cwd, index, meta, cfg)
|
|
164
|
+
} else if (tool === 'Grep') {
|
|
165
|
+
const pattern = String(ti.pattern ?? '')
|
|
166
|
+
const pathHint = ti.path ? ` (under ${ti.path})` : ''
|
|
167
|
+
reason = pattern
|
|
168
|
+
? `Tip: \`wiki_search\` MCP tool covers most symbol/file lookups across this repo. ` +
|
|
169
|
+
`Run \`use_mcp_tool('code-wiki', 'wiki_search', { query: "${pattern.replace(/"/g, '\\"')}" })\` ` +
|
|
170
|
+
`before Grep${pathHint}.`
|
|
171
|
+
: 'Tip: prefer the `wiki_search` MCP tool over Grep when looking for symbols or files.'
|
|
172
|
+
} else if (tool === 'Glob') {
|
|
173
|
+
reason =
|
|
174
|
+
`Tip: \`.codewiki/INDEX.md\` lists every module + file in the wiki. ` +
|
|
175
|
+
`Read it before globbing the source tree.`
|
|
176
|
+
} else if (
|
|
177
|
+
tool === 'Bash' &&
|
|
178
|
+
typeof ti.command === 'string' &&
|
|
179
|
+
/^(cat|head|tail|sed\s+-n|less|more|bat)\s/.test(ti.command.trim())
|
|
180
|
+
) {
|
|
181
|
+
reason =
|
|
182
|
+
`Tip: prefer \`.codewiki/files/...md\` over cat/head/tail of source files.`
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
if (!reason) return
|
|
186
|
+
|
|
187
|
+
const out = {
|
|
188
|
+
hookSpecificOutput: {
|
|
189
|
+
hookEventName: 'PreToolUse',
|
|
190
|
+
permissionDecision: 'allow',
|
|
191
|
+
permissionDecisionReason: reason,
|
|
192
|
+
},
|
|
193
|
+
}
|
|
194
|
+
process.stdout.write(JSON.stringify(out))
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
main().catch(() => process.exit(0))
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
// PostToolUse hook on Read: emit a one-line note with the wiki page path so
|
|
3
|
+
// the model remembers the wiki location and avoids re-reading the source next
|
|
4
|
+
// time.
|
|
5
|
+
|
|
6
|
+
import { promises as fs } from 'node:fs'
|
|
7
|
+
import path from 'node:path'
|
|
8
|
+
|
|
9
|
+
async function main() {
|
|
10
|
+
let payload
|
|
11
|
+
try {
|
|
12
|
+
const buf = await new Promise((res) => {
|
|
13
|
+
let chunks = ''
|
|
14
|
+
process.stdin.setEncoding('utf8')
|
|
15
|
+
process.stdin.on('data', (c) => (chunks += c))
|
|
16
|
+
process.stdin.on('end', () => res(chunks))
|
|
17
|
+
})
|
|
18
|
+
payload = JSON.parse(buf || '{}')
|
|
19
|
+
} catch {
|
|
20
|
+
payload = {}
|
|
21
|
+
}
|
|
22
|
+
const cwd = payload.cwd || process.env.CODEWIKI_CWD || process.cwd()
|
|
23
|
+
const ti = payload.tool_input || {}
|
|
24
|
+
const filePath = ti.file_path
|
|
25
|
+
if (!filePath) return
|
|
26
|
+
|
|
27
|
+
let rel = path.isAbsolute(filePath) ? path.relative(cwd, filePath) : filePath
|
|
28
|
+
rel = rel.split(path.sep).join('/')
|
|
29
|
+
if (rel.startsWith('./')) rel = rel.slice(2)
|
|
30
|
+
if (rel.startsWith('.codewiki/')) return
|
|
31
|
+
|
|
32
|
+
const wikiPage = `.codewiki/files/${rel}.md`
|
|
33
|
+
try {
|
|
34
|
+
await fs.stat(path.join(cwd, wikiPage))
|
|
35
|
+
} catch {
|
|
36
|
+
return
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
// M5 will pull symbol hints from .index.json. For now, just the file page.
|
|
40
|
+
let hint = ''
|
|
41
|
+
try {
|
|
42
|
+
const idx = JSON.parse(await fs.readFile(path.join(cwd, '.codewiki', '.index.json'), 'utf8'))
|
|
43
|
+
const fileEntry = idx.files?.find((x) => x.path === rel)
|
|
44
|
+
if (fileEntry?.tokens) {
|
|
45
|
+
hint = ` (~${fileEntry.tokens} tokens vs the source file's many more)`
|
|
46
|
+
}
|
|
47
|
+
} catch {
|
|
48
|
+
/* ignore */
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
process.stdout.write(
|
|
52
|
+
`<system-reminder>\nFor future reference, the wiki summary for \`${rel}\` is at \`${wikiPage}\`${hint}.\n</system-reminder>\n`,
|
|
53
|
+
)
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
main().catch(() => process.exit(0))
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
// PostToolUse hook on Write | Edit | MultiEdit:
|
|
3
|
+
// Mark the affected file for incremental re-render.
|
|
4
|
+
// Plus a small PostToolUse reminder: log the wiki page for the touched file
|
|
5
|
+
// so the model sees an inline pointer in subsequent requests.
|
|
6
|
+
|
|
7
|
+
import { promises as fs } from 'node:fs'
|
|
8
|
+
import path from 'node:path'
|
|
9
|
+
import { spawnSync } from 'node:child_process'
|
|
10
|
+
|
|
11
|
+
async function main() {
|
|
12
|
+
let payload
|
|
13
|
+
try {
|
|
14
|
+
const buf = await new Promise((res) => {
|
|
15
|
+
let chunks = ''
|
|
16
|
+
process.stdin.setEncoding('utf8')
|
|
17
|
+
process.stdin.on('data', (c) => (chunks += c))
|
|
18
|
+
process.stdin.on('end', () => res(chunks))
|
|
19
|
+
})
|
|
20
|
+
payload = JSON.parse(buf || '{}')
|
|
21
|
+
} catch {
|
|
22
|
+
payload = {}
|
|
23
|
+
}
|
|
24
|
+
const cwd = payload.cwd || process.env.CODEWIKI_CWD || process.cwd()
|
|
25
|
+
|
|
26
|
+
const ti = payload.tool_input || {}
|
|
27
|
+
const filePath = ti.file_path || ti.path
|
|
28
|
+
if (!filePath) return
|
|
29
|
+
|
|
30
|
+
let rel = path.isAbsolute(filePath) ? path.relative(cwd, filePath) : filePath
|
|
31
|
+
rel = rel.split(path.sep).join('/')
|
|
32
|
+
if (rel.startsWith('./')) rel = rel.slice(2)
|
|
33
|
+
if (!rel || rel.startsWith('.codewiki/')) return
|
|
34
|
+
|
|
35
|
+
// Mark in .state.json for incremental rebuild.
|
|
36
|
+
// Path: this script lives at hooks/post-tool-use.mjs inside the plugin root.
|
|
37
|
+
// The bundled CLI is at dist/codewiki.mjs.
|
|
38
|
+
const here = new URL(import.meta.url).pathname.replace(/^\//, '')
|
|
39
|
+
const hooksDir = path.dirname(here)
|
|
40
|
+
const pluginRoot = path.dirname(hooksDir)
|
|
41
|
+
spawnSync(
|
|
42
|
+
'node',
|
|
43
|
+
[path.join(pluginRoot, 'dist', 'codewiki.mjs'), 'invalidate', rel],
|
|
44
|
+
{ cwd, stdio: 'ignore' },
|
|
45
|
+
)
|
|
46
|
+
|
|
47
|
+
// The inline reminder (Claude Code shows it inline after the tool result).
|
|
48
|
+
const wikiPage = `.codewiki/files/${rel}.md`
|
|
49
|
+
// Verify the wiki page actually exists; otherwise skip.
|
|
50
|
+
try {
|
|
51
|
+
await fs.stat(path.join(cwd, wikiPage))
|
|
52
|
+
} catch {
|
|
53
|
+
return
|
|
54
|
+
}
|
|
55
|
+
process.stdout.write(
|
|
56
|
+
`<system-reminder>\nFile \`${rel}\` was just edited. Its wiki page is at \`${wikiPage}\` and may now be stale. Run /wiki-refresh (or just continue — the next refresh will pick it up).\n</system-reminder>\n`,
|
|
57
|
+
)
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
main().catch(() => process.exit(0))
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
// PreCompact hook: write a marker file so SessionStart can detect that the
|
|
3
|
+
// session was compacted and trigger a smart refresh in the background.
|
|
4
|
+
|
|
5
|
+
import { promises as fs } from 'node:fs'
|
|
6
|
+
import path from 'node:path'
|
|
7
|
+
|
|
8
|
+
async function main() {
|
|
9
|
+
const cwd = process.env.CODEWIKI_CWD || process.cwd()
|
|
10
|
+
const marker = path.join(cwd, '.codewiki', '.last-compact')
|
|
11
|
+
try {
|
|
12
|
+
await fs.mkdir(path.dirname(marker), { recursive: true })
|
|
13
|
+
await fs.writeFile(
|
|
14
|
+
marker,
|
|
15
|
+
JSON.stringify({
|
|
16
|
+
at: new Date().toISOString(),
|
|
17
|
+
head: process.env.CODEWIKI_GIT_HEAD || 'uncommitted',
|
|
18
|
+
}),
|
|
19
|
+
)
|
|
20
|
+
} catch {
|
|
21
|
+
/* never block compact */
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
main().catch(() => process.exit(0))
|