codebase-wiki 0.1.0 → 0.2.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/CHANGELOG.md +18 -0
- package/dist/CHANGELOG.md +18 -0
- package/dist/chunks/build.mjs +4 -75
- package/dist/chunks/config.mjs +3 -60
- package/dist/chunks/llm.mjs +3 -6
- package/dist/code-wiki-mcp.mjs +2 -1
- package/dist/code-wiki.mjs +7 -5
- package/dist/codewiki.mjs +25 -4
- package/dist/shared/codebase-wiki.CzOYKZWm.mjs +57 -0
- package/dist/shared/codebase-wiki.Wp4qpPB-.mjs +75 -0
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -1,8 +1,24 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 0.2.0
|
|
4
|
+
|
|
5
|
+
### Minor Changes
|
|
6
|
+
|
|
7
|
+
- 03a4d29: Initial v0.1.0 release of `code-wiki`.
|
|
8
|
+
|
|
9
|
+
- M1: scaffold + walker + minimal render.
|
|
10
|
+
- M2: tree-sitter extraction for TS/JS/TSX/JSX.
|
|
11
|
+
- M3: 7 more language extractors (Python / Go / Rust / Java / C / C++ / Ruby).
|
|
12
|
+
- M4: graph extraction — imports + dependents; usable by `wiki_callers` / `wiki_callees` MCP tools.
|
|
13
|
+
- M5: MCP resource pagination, smarter auto-nudge hooks (config-driven, freshness-aware).
|
|
14
|
+
- M6: incremental refresh — `codewiki refresh` uses git diff; `codewiki watch` runs chokidar.
|
|
15
|
+
- M7: optional LLM enrichment — file-level Haiku + module-level Sonnet, content-hash cached.
|
|
16
|
+
- M8: Mermaid diagrams in architecture.md and module pages.
|
|
17
|
+
|
|
3
18
|
## [Unreleased]
|
|
4
19
|
|
|
5
20
|
### Fixed
|
|
21
|
+
|
|
6
22
|
- Slash commands now invoke `${CLAUDE_PLUGIN_ROOT}/dist/codewiki.mjs` directly
|
|
7
23
|
instead of relying on a globally-installed `codewiki` binary. Plugin is fully
|
|
8
24
|
self-contained.
|
|
@@ -18,11 +34,13 @@
|
|
|
18
34
|
similar packages had escaped inlining under the old config).
|
|
19
35
|
|
|
20
36
|
### Pending
|
|
37
|
+
|
|
21
38
|
- v0.1.0 tag — all 9 milestones + install fix ready.
|
|
22
39
|
|
|
23
40
|
## v0.1.0 (in progress, target tag)
|
|
24
41
|
|
|
25
42
|
### Added
|
|
43
|
+
|
|
26
44
|
- M1: scaffolding, walker, minimal render, hooks, MCP server stub.
|
|
27
45
|
- M2: tree-sitter extraction (TS / JS / TSX / JSX).
|
|
28
46
|
- M3: 7 more language extractors (Python / Go / Rust / Java / C / C++ / Ruby).
|
package/dist/CHANGELOG.md
CHANGED
|
@@ -1,8 +1,24 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 0.2.0
|
|
4
|
+
|
|
5
|
+
### Minor Changes
|
|
6
|
+
|
|
7
|
+
- 03a4d29: Initial v0.1.0 release of `code-wiki`.
|
|
8
|
+
|
|
9
|
+
- M1: scaffold + walker + minimal render.
|
|
10
|
+
- M2: tree-sitter extraction for TS/JS/TSX/JSX.
|
|
11
|
+
- M3: 7 more language extractors (Python / Go / Rust / Java / C / C++ / Ruby).
|
|
12
|
+
- M4: graph extraction — imports + dependents; usable by `wiki_callers` / `wiki_callees` MCP tools.
|
|
13
|
+
- M5: MCP resource pagination, smarter auto-nudge hooks (config-driven, freshness-aware).
|
|
14
|
+
- M6: incremental refresh — `codewiki refresh` uses git diff; `codewiki watch` runs chokidar.
|
|
15
|
+
- M7: optional LLM enrichment — file-level Haiku + module-level Sonnet, content-hash cached.
|
|
16
|
+
- M8: Mermaid diagrams in architecture.md and module pages.
|
|
17
|
+
|
|
3
18
|
## [Unreleased]
|
|
4
19
|
|
|
5
20
|
### Fixed
|
|
21
|
+
|
|
6
22
|
- Slash commands now invoke `${CLAUDE_PLUGIN_ROOT}/dist/codewiki.mjs` directly
|
|
7
23
|
instead of relying on a globally-installed `codewiki` binary. Plugin is fully
|
|
8
24
|
self-contained.
|
|
@@ -18,11 +34,13 @@
|
|
|
18
34
|
similar packages had escaped inlining under the old config).
|
|
19
35
|
|
|
20
36
|
### Pending
|
|
37
|
+
|
|
21
38
|
- v0.1.0 tag — all 9 milestones + install fix ready.
|
|
22
39
|
|
|
23
40
|
## v0.1.0 (in progress, target tag)
|
|
24
41
|
|
|
25
42
|
### Added
|
|
43
|
+
|
|
26
44
|
- M1: scaffolding, walker, minimal render, hooks, MCP server stub.
|
|
27
45
|
- M2: tree-sitter extraction (TS / JS / TSX / JSX).
|
|
28
46
|
- M3: 7 more language extractors (Python / Go / Rust / Java / C / C++ / Ruby).
|
package/dist/chunks/build.mjs
CHANGED
|
@@ -1,10 +1,11 @@
|
|
|
1
1
|
import path__default from 'node:path';
|
|
2
2
|
import { promises } from 'node:fs';
|
|
3
|
-
import {
|
|
3
|
+
import { b as repoRel, l as log, t as toPosix, c as child, w as writeJson, a as atomicWriteText, e as exists } from '../shared/codebase-wiki.CzOYKZWm.mjs';
|
|
4
|
+
import { loadConfig, configSummary } from './config.mjs';
|
|
4
5
|
import ignore from 'ignore';
|
|
5
6
|
import { globby } from 'globby';
|
|
7
|
+
import { c as countTokens, g as groupModules, m as moduleForFile } from '../shared/codebase-wiki.Wp4qpPB-.mjs';
|
|
6
8
|
import { createHash } from 'node:crypto';
|
|
7
|
-
import { encode } from 'gpt-tokenizer/encoding/cl100k_base';
|
|
8
9
|
import { Language, Parser, Query } from 'web-tree-sitter';
|
|
9
10
|
|
|
10
11
|
const EXT_TO_LANG = {
|
|
@@ -83,82 +84,10 @@ const walker = {
|
|
|
83
84
|
walkFiles: walkFiles
|
|
84
85
|
};
|
|
85
86
|
|
|
86
|
-
const TEST_PATTERNS = [
|
|
87
|
-
/(^|\/)__tests__\//,
|
|
88
|
-
/\.test\.[a-z]+$/,
|
|
89
|
-
/\.spec\.[a-z]+$/,
|
|
90
|
-
/(^|\/)test_(.+)\.[a-z]+$/,
|
|
91
|
-
/(^|\/)(.+)_test\.[a-z]+$/,
|
|
92
|
-
/(^|\/)spec\//
|
|
93
|
-
];
|
|
94
|
-
function isTestFile(repoPath) {
|
|
95
|
-
return TEST_PATTERNS.some((re) => re.test(repoPath));
|
|
96
|
-
}
|
|
97
|
-
function moduleForFile(repoPath, cfg) {
|
|
98
|
-
if (isTestFile(repoPath)) return "_tests";
|
|
99
|
-
for (const [from, to] of Object.entries(cfg.modules.alias)) {
|
|
100
|
-
if (repoPath === from || repoPath.startsWith(`${from}/`)) {
|
|
101
|
-
const remainder = repoPath.slice(from.length).replace(/^\//, "");
|
|
102
|
-
const top2 = remainder.split("/")[0] || "_root";
|
|
103
|
-
const candidate = `${to}/${remainder === "" ? "" : top2}`.replace(/\/$/, "");
|
|
104
|
-
return candidate.split("/")[0] || "_root";
|
|
105
|
-
}
|
|
106
|
-
}
|
|
107
|
-
const top = repoPath.split("/")[0];
|
|
108
|
-
return top || "_root";
|
|
109
|
-
}
|
|
110
|
-
function groupModules(files, cfg) {
|
|
111
|
-
const map = /* @__PURE__ */ new Map();
|
|
112
|
-
for (const f of files) {
|
|
113
|
-
if (f.repoPath.startsWith(".codewiki/")) continue;
|
|
114
|
-
const id = moduleForFile(f.repoPath, cfg);
|
|
115
|
-
let entry = map.get(id);
|
|
116
|
-
if (!entry) {
|
|
117
|
-
entry = {
|
|
118
|
-
id,
|
|
119
|
-
title: id,
|
|
120
|
-
files: [],
|
|
121
|
-
publicSurface: []
|
|
122
|
-
};
|
|
123
|
-
map.set(id, entry);
|
|
124
|
-
}
|
|
125
|
-
entry.files.push(f.repoPath);
|
|
126
|
-
for (const s of f.publicSymbols) {
|
|
127
|
-
entry.publicSurface.push(`${s.kind === "method" ? "method" : s.kind} ${s.name}`);
|
|
128
|
-
}
|
|
129
|
-
}
|
|
130
|
-
for (const id of [...map.keys()]) {
|
|
131
|
-
if (cfg.modules.ignore.includes(id)) {
|
|
132
|
-
map.delete(id);
|
|
133
|
-
}
|
|
134
|
-
}
|
|
135
|
-
return map;
|
|
136
|
-
}
|
|
137
|
-
|
|
138
87
|
function sha256Hex(input) {
|
|
139
88
|
return createHash("sha256").update(input).digest("hex");
|
|
140
89
|
}
|
|
141
90
|
|
|
142
|
-
function countTokens(input) {
|
|
143
|
-
if (!input) return 0;
|
|
144
|
-
return encode(input).length;
|
|
145
|
-
}
|
|
146
|
-
function truncateToTokens(text, budget, ellipsis = "\u2026") {
|
|
147
|
-
const current = countTokens(text);
|
|
148
|
-
if (current <= budget) return text;
|
|
149
|
-
let lo = 0;
|
|
150
|
-
let hi = text.length;
|
|
151
|
-
while (lo < hi) {
|
|
152
|
-
const mid = lo + hi + 1 >> 1;
|
|
153
|
-
if (countTokens(text.slice(0, mid)) <= budget - 1) {
|
|
154
|
-
lo = mid;
|
|
155
|
-
} else {
|
|
156
|
-
hi = mid - 1;
|
|
157
|
-
}
|
|
158
|
-
}
|
|
159
|
-
return text.slice(0, lo).trimEnd() + (lo > 0 ? " " + ellipsis : "");
|
|
160
|
-
}
|
|
161
|
-
|
|
162
91
|
const GENERATOR = `code-wiki`;
|
|
163
92
|
const VERSION = "0.1.0";
|
|
164
93
|
function frontmatter(page) {
|
|
@@ -1973,4 +1902,4 @@ async function replaceDir(target, replacement) {
|
|
|
1973
1902
|
await promises.rename(replacement, target);
|
|
1974
1903
|
}
|
|
1975
1904
|
|
|
1976
|
-
export { walker as a, buildWiki as b,
|
|
1905
|
+
export { walker as a, buildWiki as b, detectLanguage as d, walkFiles as w };
|
package/dist/chunks/config.mjs
CHANGED
|
@@ -1,59 +1,9 @@
|
|
|
1
|
-
import pino from 'pino';
|
|
2
1
|
import { promises } from 'node:fs';
|
|
3
2
|
import path__default from 'node:path';
|
|
3
|
+
import { r as readJson, t as toPosix } from '../shared/codebase-wiki.CzOYKZWm.mjs';
|
|
4
|
+
import 'pino';
|
|
4
5
|
import 'node:os';
|
|
5
6
|
|
|
6
|
-
const level = process.env.CODEWIKI_LOG ?? (process.env.NODE_ENV === "test" ? "silent" : "info");
|
|
7
|
-
const log = pino({
|
|
8
|
-
level,
|
|
9
|
-
base: { name: "code-wiki" },
|
|
10
|
-
timestamp: pino.stdTimeFunctions.isoTime
|
|
11
|
-
});
|
|
12
|
-
const child = (bindings) => log.child(bindings);
|
|
13
|
-
|
|
14
|
-
function toPosix(p) {
|
|
15
|
-
return p.replace(/\\/g, "/");
|
|
16
|
-
}
|
|
17
|
-
async function exists(filePath) {
|
|
18
|
-
try {
|
|
19
|
-
await promises.access(filePath);
|
|
20
|
-
return true;
|
|
21
|
-
} catch {
|
|
22
|
-
return false;
|
|
23
|
-
}
|
|
24
|
-
}
|
|
25
|
-
async function readText(filePath) {
|
|
26
|
-
return promises.readFile(filePath, "utf8");
|
|
27
|
-
}
|
|
28
|
-
async function readJson(filePath) {
|
|
29
|
-
try {
|
|
30
|
-
const text = await readText(filePath);
|
|
31
|
-
return JSON.parse(text);
|
|
32
|
-
} catch {
|
|
33
|
-
return null;
|
|
34
|
-
}
|
|
35
|
-
}
|
|
36
|
-
async function writeText(filePath, content) {
|
|
37
|
-
const dir = path__default.dirname(filePath);
|
|
38
|
-
await promises.mkdir(dir, { recursive: true });
|
|
39
|
-
await promises.writeFile(filePath, content, "utf8");
|
|
40
|
-
}
|
|
41
|
-
async function writeJson(filePath, value) {
|
|
42
|
-
const text = JSON.stringify(value, null, 2) + "\n";
|
|
43
|
-
await writeText(filePath, text);
|
|
44
|
-
}
|
|
45
|
-
async function atomicWriteText(target, content) {
|
|
46
|
-
const dir = path__default.dirname(target);
|
|
47
|
-
await promises.mkdir(dir, { recursive: true });
|
|
48
|
-
const tmp = path__default.join(dir, `.tmp.${process.pid}.${Date.now()}.${Math.random().toString(36).slice(2, 8)}`);
|
|
49
|
-
await promises.writeFile(tmp, content, "utf8");
|
|
50
|
-
await promises.rename(tmp, target);
|
|
51
|
-
}
|
|
52
|
-
function repoRel(absolute, root) {
|
|
53
|
-
const rel = path__default.relative(root, absolute);
|
|
54
|
-
return toPosix(rel).replace(/^\.\//, "");
|
|
55
|
-
}
|
|
56
|
-
|
|
57
7
|
const DEFAULT_CONFIG = {
|
|
58
8
|
outDir: ".codewiki",
|
|
59
9
|
sourceRoots: ["."],
|
|
@@ -164,11 +114,4 @@ function configSummary(c) {
|
|
|
164
114
|
].join(" ");
|
|
165
115
|
}
|
|
166
116
|
|
|
167
|
-
|
|
168
|
-
__proto__: null,
|
|
169
|
-
DEFAULT_CONFIG: DEFAULT_CONFIG,
|
|
170
|
-
configSummary: configSummary,
|
|
171
|
-
loadConfig: loadConfig
|
|
172
|
-
};
|
|
173
|
-
|
|
174
|
-
export { DEFAULT_CONFIG as D, log as a, atomicWriteText as b, repoRel as c, child as d, exists as e, configSummary as f, config as g, loadConfig as l, readJson as r, toPosix as t, writeJson as w };
|
|
117
|
+
export { DEFAULT_CONFIG, configSummary, loadConfig };
|
package/dist/chunks/llm.mjs
CHANGED
|
@@ -4,14 +4,11 @@ import { createHash } from 'node:crypto';
|
|
|
4
4
|
import Anthropic from '@anthropic-ai/sdk';
|
|
5
5
|
import PQueue from 'p-queue';
|
|
6
6
|
import pRetry from 'p-retry';
|
|
7
|
-
import {
|
|
8
|
-
import { g as groupModules, c as countTokens } from '
|
|
7
|
+
import { l as log, r as readJson, w as writeJson } from '../shared/codebase-wiki.CzOYKZWm.mjs';
|
|
8
|
+
import { g as groupModules, c as countTokens } from '../shared/codebase-wiki.Wp4qpPB-.mjs';
|
|
9
9
|
import 'pino';
|
|
10
10
|
import 'node:os';
|
|
11
|
-
import 'ignore';
|
|
12
|
-
import 'globby';
|
|
13
11
|
import 'gpt-tokenizer/encoding/cl100k_base';
|
|
14
|
-
import 'web-tree-sitter';
|
|
15
12
|
|
|
16
13
|
const SYSTEM_PROMPT = "You are a precise code summarizer. Output ONLY a one-line PURPOSE and a 1-3 sentence NOTES section. No bullet lists, no headings, no markdown formatting. Tone: terse, technical, factual.";
|
|
17
14
|
function client(opts) {
|
|
@@ -188,7 +185,7 @@ async function enrichAll(opts) {
|
|
|
188
185
|
return { files, modules };
|
|
189
186
|
}
|
|
190
187
|
async function runEnrichmentCli(cwd, opts = {}) {
|
|
191
|
-
const { loadConfig } = await import('./config.mjs')
|
|
188
|
+
const { loadConfig } = await import('./config.mjs');
|
|
192
189
|
const { walkFiles } = await import('./build.mjs').then(function (n) { return n.a; });
|
|
193
190
|
const cfg = await loadConfig(cwd);
|
|
194
191
|
const walked = await walkFiles({ root: cwd, ignoreGlobs: cfg.ignore });
|
package/dist/code-wiki-mcp.mjs
CHANGED
|
@@ -4,7 +4,8 @@ import path__default from 'node:path';
|
|
|
4
4
|
import { Server } from '@modelcontextprotocol/sdk/server/index.js';
|
|
5
5
|
import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js';
|
|
6
6
|
import { ListToolsRequestSchema, ListResourcesRequestSchema, ReadResourceRequestSchema, CallToolRequestSchema } from '@modelcontextprotocol/sdk/types.js';
|
|
7
|
-
import {
|
|
7
|
+
import { r as readJson, l as log } from './shared/codebase-wiki.CzOYKZWm.mjs';
|
|
8
|
+
import { loadConfig } from './chunks/config.mjs';
|
|
8
9
|
import 'pino';
|
|
9
10
|
import 'node:fs';
|
|
10
11
|
import 'node:os';
|
package/dist/code-wiki.mjs
CHANGED
|
@@ -1,11 +1,13 @@
|
|
|
1
|
-
export { b as buildWiki,
|
|
2
|
-
export {
|
|
1
|
+
export { b as buildWiki, d as detectLanguage, w as walkFiles } from './chunks/build.mjs';
|
|
2
|
+
export { DEFAULT_CONFIG, loadConfig } from './chunks/config.mjs';
|
|
3
|
+
export { c as countTokens, g as groupModules, m as moduleForFile, t as truncateToTokens } from './shared/codebase-wiki.Wp4qpPB-.mjs';
|
|
3
4
|
import 'node:path';
|
|
4
5
|
import 'node:fs';
|
|
6
|
+
import './shared/codebase-wiki.CzOYKZWm.mjs';
|
|
7
|
+
import 'pino';
|
|
8
|
+
import 'node:os';
|
|
5
9
|
import 'ignore';
|
|
6
10
|
import 'globby';
|
|
7
11
|
import 'node:crypto';
|
|
8
|
-
import 'gpt-tokenizer/encoding/cl100k_base';
|
|
9
12
|
import 'web-tree-sitter';
|
|
10
|
-
import '
|
|
11
|
-
import 'node:os';
|
|
13
|
+
import 'gpt-tokenizer/encoding/cl100k_base';
|
package/dist/codewiki.mjs
CHANGED
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
import process$1 from 'node:process';
|
|
3
3
|
import * as path from 'node:path';
|
|
4
4
|
import path__default, { resolve, join, relative, sep } from 'node:path';
|
|
5
|
+
import { fileURLToPath } from 'node:url';
|
|
5
6
|
import { Command } from 'commander';
|
|
6
7
|
import { EventEmitter } from 'node:events';
|
|
7
8
|
import { unwatchFile, watchFile, watch as watch$1, stat as stat$1 } from 'node:fs';
|
|
@@ -9,12 +10,14 @@ import { lstat, stat, readdir, realpath, open } from 'node:fs/promises';
|
|
|
9
10
|
import { Readable } from 'node:stream';
|
|
10
11
|
import { type } from 'node:os';
|
|
11
12
|
import { b as buildWiki } from './chunks/build.mjs';
|
|
12
|
-
import {
|
|
13
|
+
import { loadConfig } from './chunks/config.mjs';
|
|
14
|
+
import { r as readJson, w as writeJson, a as atomicWriteText, e as exists } from './shared/codebase-wiki.CzOYKZWm.mjs';
|
|
13
15
|
import { simpleGit } from 'simple-git';
|
|
14
16
|
import 'ignore';
|
|
15
17
|
import 'globby';
|
|
16
|
-
import '
|
|
18
|
+
import './shared/codebase-wiki.Wp4qpPB-.mjs';
|
|
17
19
|
import 'gpt-tokenizer/encoding/cl100k_base';
|
|
20
|
+
import 'node:crypto';
|
|
18
21
|
import 'web-tree-sitter';
|
|
19
22
|
import 'pino';
|
|
20
23
|
|
|
@@ -1768,7 +1771,24 @@ async function enqueueInvalidation(cwd, repoPath) {
|
|
|
1768
1771
|
await atomicWriteText(p, JSON.stringify(state, null, 2));
|
|
1769
1772
|
}
|
|
1770
1773
|
|
|
1771
|
-
const
|
|
1774
|
+
const here = path__default.dirname(fileURLToPath(import.meta.url));
|
|
1775
|
+
let pkgVersion = "0.0.0";
|
|
1776
|
+
for (const candidate of [
|
|
1777
|
+
path__default.join(here, "..", "package.json"),
|
|
1778
|
+
path__default.join(here, "..", "..", "package.json"),
|
|
1779
|
+
path__default.join(here, "..", "..", "..", "package.json")
|
|
1780
|
+
]) {
|
|
1781
|
+
try {
|
|
1782
|
+
const txt = await import('node:fs/promises').then((m) => m.readFile(candidate, "utf8"));
|
|
1783
|
+
const parsed = JSON.parse(txt);
|
|
1784
|
+
if (parsed?.name === "codebase-wiki" && parsed.version) {
|
|
1785
|
+
pkgVersion = parsed.version;
|
|
1786
|
+
break;
|
|
1787
|
+
}
|
|
1788
|
+
} catch {
|
|
1789
|
+
}
|
|
1790
|
+
}
|
|
1791
|
+
const pkg = { version: pkgVersion };
|
|
1772
1792
|
async function main() {
|
|
1773
1793
|
const program = new Command();
|
|
1774
1794
|
program.name("codewiki").description("Token-efficient code wiki generator and Claude Code plugin entry point").version(pkg.version).option("--cwd <path>", "project root", process$1.cwd());
|
|
@@ -1935,11 +1955,12 @@ async function main() {
|
|
|
1935
1955
|
const cfg = await loadConfig(cwd);
|
|
1936
1956
|
const wikiBase = path__default.join(cwd, cfg.outDir);
|
|
1937
1957
|
const rel = String(pathOrSymbol).replace(/^\.\//, "");
|
|
1958
|
+
const symbolPath = rel.includes("::") ? rel.replace(/::/g, "/") + ".md" : null;
|
|
1938
1959
|
const candidates = [
|
|
1939
1960
|
path__default.join(wikiBase, rel.endsWith(".md") ? rel : `${rel}.md`),
|
|
1940
1961
|
path__default.join(wikiBase, "files", `${rel}.md`),
|
|
1941
1962
|
path__default.join(wikiBase, "modules", `${rel}.md`),
|
|
1942
|
-
path__default.join(wikiBase, "symbols",
|
|
1963
|
+
...symbolPath ? [path__default.join(wikiBase, "symbols", symbolPath)] : []
|
|
1943
1964
|
];
|
|
1944
1965
|
for (const c of candidates) {
|
|
1945
1966
|
if (await exists(c)) {
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
import pino from 'pino';
|
|
2
|
+
import { promises } from 'node:fs';
|
|
3
|
+
import path__default from 'node:path';
|
|
4
|
+
import 'node:os';
|
|
5
|
+
|
|
6
|
+
const level = process.env.CODEWIKI_LOG ?? (process.env.NODE_ENV === "test" ? "silent" : "info");
|
|
7
|
+
const log = pino({
|
|
8
|
+
level,
|
|
9
|
+
base: { name: "code-wiki" },
|
|
10
|
+
timestamp: pino.stdTimeFunctions.isoTime
|
|
11
|
+
});
|
|
12
|
+
const child = (bindings) => log.child(bindings);
|
|
13
|
+
|
|
14
|
+
function toPosix(p) {
|
|
15
|
+
return p.replace(/\\/g, "/");
|
|
16
|
+
}
|
|
17
|
+
async function exists(filePath) {
|
|
18
|
+
try {
|
|
19
|
+
await promises.access(filePath);
|
|
20
|
+
return true;
|
|
21
|
+
} catch {
|
|
22
|
+
return false;
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
async function readText(filePath) {
|
|
26
|
+
return promises.readFile(filePath, "utf8");
|
|
27
|
+
}
|
|
28
|
+
async function readJson(filePath) {
|
|
29
|
+
try {
|
|
30
|
+
const text = await readText(filePath);
|
|
31
|
+
return JSON.parse(text);
|
|
32
|
+
} catch {
|
|
33
|
+
return null;
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
async function writeText(filePath, content) {
|
|
37
|
+
const dir = path__default.dirname(filePath);
|
|
38
|
+
await promises.mkdir(dir, { recursive: true });
|
|
39
|
+
await promises.writeFile(filePath, content, "utf8");
|
|
40
|
+
}
|
|
41
|
+
async function writeJson(filePath, value) {
|
|
42
|
+
const text = JSON.stringify(value, null, 2) + "\n";
|
|
43
|
+
await writeText(filePath, text);
|
|
44
|
+
}
|
|
45
|
+
async function atomicWriteText(target, content) {
|
|
46
|
+
const dir = path__default.dirname(target);
|
|
47
|
+
await promises.mkdir(dir, { recursive: true });
|
|
48
|
+
const tmp = path__default.join(dir, `.tmp.${process.pid}.${Date.now()}.${Math.random().toString(36).slice(2, 8)}`);
|
|
49
|
+
await promises.writeFile(tmp, content, "utf8");
|
|
50
|
+
await promises.rename(tmp, target);
|
|
51
|
+
}
|
|
52
|
+
function repoRel(absolute, root) {
|
|
53
|
+
const rel = path__default.relative(root, absolute);
|
|
54
|
+
return toPosix(rel).replace(/^\.\//, "");
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
export { atomicWriteText as a, repoRel as b, child as c, exists as e, log as l, readJson as r, toPosix as t, writeJson as w };
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
import { encode } from 'gpt-tokenizer/encoding/cl100k_base';
|
|
2
|
+
|
|
3
|
+
const TEST_PATTERNS = [
|
|
4
|
+
/(^|\/)__tests__\//,
|
|
5
|
+
/\.test\.[a-z]+$/,
|
|
6
|
+
/\.spec\.[a-z]+$/,
|
|
7
|
+
/(^|\/)test_(.+)\.[a-z]+$/,
|
|
8
|
+
/(^|\/)(.+)_test\.[a-z]+$/,
|
|
9
|
+
/(^|\/)spec\//
|
|
10
|
+
];
|
|
11
|
+
function isTestFile(repoPath) {
|
|
12
|
+
return TEST_PATTERNS.some((re) => re.test(repoPath));
|
|
13
|
+
}
|
|
14
|
+
function moduleForFile(repoPath, cfg) {
|
|
15
|
+
if (isTestFile(repoPath)) return "_tests";
|
|
16
|
+
for (const [from, to] of Object.entries(cfg.modules.alias)) {
|
|
17
|
+
if (repoPath === from || repoPath.startsWith(`${from}/`)) {
|
|
18
|
+
const remainder = repoPath.slice(from.length).replace(/^\//, "");
|
|
19
|
+
const top2 = remainder.split("/")[0] || "_root";
|
|
20
|
+
const candidate = `${to}/${remainder === "" ? "" : top2}`.replace(/\/$/, "");
|
|
21
|
+
return candidate.split("/")[0] || "_root";
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
const top = repoPath.split("/")[0];
|
|
25
|
+
return top || "_root";
|
|
26
|
+
}
|
|
27
|
+
function groupModules(files, cfg) {
|
|
28
|
+
const map = /* @__PURE__ */ new Map();
|
|
29
|
+
for (const f of files) {
|
|
30
|
+
if (f.repoPath.startsWith(".codewiki/")) continue;
|
|
31
|
+
const id = moduleForFile(f.repoPath, cfg);
|
|
32
|
+
let entry = map.get(id);
|
|
33
|
+
if (!entry) {
|
|
34
|
+
entry = {
|
|
35
|
+
id,
|
|
36
|
+
title: id,
|
|
37
|
+
files: [],
|
|
38
|
+
publicSurface: []
|
|
39
|
+
};
|
|
40
|
+
map.set(id, entry);
|
|
41
|
+
}
|
|
42
|
+
entry.files.push(f.repoPath);
|
|
43
|
+
for (const s of f.publicSymbols) {
|
|
44
|
+
entry.publicSurface.push(`${s.kind === "method" ? "method" : s.kind} ${s.name}`);
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
for (const id of [...map.keys()]) {
|
|
48
|
+
if (cfg.modules.ignore.includes(id)) {
|
|
49
|
+
map.delete(id);
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
return map;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
function countTokens(input) {
|
|
56
|
+
if (!input) return 0;
|
|
57
|
+
return encode(input).length;
|
|
58
|
+
}
|
|
59
|
+
function truncateToTokens(text, budget, ellipsis = "\u2026") {
|
|
60
|
+
const current = countTokens(text);
|
|
61
|
+
if (current <= budget) return text;
|
|
62
|
+
let lo = 0;
|
|
63
|
+
let hi = text.length;
|
|
64
|
+
while (lo < hi) {
|
|
65
|
+
const mid = lo + hi + 1 >> 1;
|
|
66
|
+
if (countTokens(text.slice(0, mid)) <= budget - 1) {
|
|
67
|
+
lo = mid;
|
|
68
|
+
} else {
|
|
69
|
+
hi = mid - 1;
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
return text.slice(0, lo).trimEnd() + (lo > 0 ? " " + ellipsis : "");
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
export { countTokens as c, groupModules as g, moduleForFile as m, truncateToTokens as t };
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "codebase-wiki",
|
|
3
|
-
"version": "0.1
|
|
3
|
+
"version": "0.2.1",
|
|
4
4
|
"description": "Token-efficient code wiki for Claude Code. Generates a browsable .codewiki/ index so models read summaries on-demand.",
|
|
5
5
|
"homepage": "https://github.com/harrycjs/code-wiki",
|
|
6
6
|
"repository": {
|