project-librarian 0.2.0 → 0.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.ko.md +260 -120
- package/README.md +203 -114
- package/SKILL.md +38 -18
- package/dist/args.js +20 -2
- package/dist/code-index-file-policy.js +107 -2
- package/dist/code-index.js +77 -59
- package/dist/hooks.js +143 -7
- package/dist/init-project-wiki.js +237 -145
- package/dist/install-skill.js +24 -12
- package/dist/mcp-server.js +449 -0
- package/dist/migration.js +1064 -50
- package/dist/modes.js +324 -149
- package/dist/taxonomy.js +193 -0
- package/dist/templates.js +241 -42
- package/dist/wiki-files.js +24 -28
- package/dist/wiki-graph.js +141 -0
- package/package.json +11 -8
- package/README.ja.md +0 -227
- package/README.zh.md +0 -227
package/dist/wiki-files.js
CHANGED
|
@@ -46,16 +46,17 @@ exports.extractWikiLinks = extractWikiLinks;
|
|
|
46
46
|
exports.wikiTitleForFile = wikiTitleForFile;
|
|
47
47
|
exports.metadataSummary = metadataSummary;
|
|
48
48
|
exports.stripMarkedSection = stripMarkedSection;
|
|
49
|
-
exports.extractMarkedSection = extractMarkedSection;
|
|
50
|
-
exports.withPreservedMarkedSections = withPreservedMarkedSections;
|
|
51
49
|
exports.hasGlossaryNeedSignal = hasGlossaryNeedSignal;
|
|
52
50
|
exports.hasGlossaryTable = hasGlossaryTable;
|
|
51
|
+
exports.firstTldrBullet = firstTldrBullet;
|
|
53
52
|
exports.canonicalBodyForLint = canonicalBodyForLint;
|
|
54
53
|
const fs = __importStar(require("node:fs"));
|
|
55
54
|
const path = __importStar(require("node:path"));
|
|
56
55
|
const workspace_1 = require("./workspace");
|
|
57
56
|
exports.standardWikiFiles = new Set([
|
|
58
57
|
"AGENTS.md",
|
|
58
|
+
"CLAUDE.md",
|
|
59
|
+
"GEMINI.md",
|
|
59
60
|
"wiki/AGENTS.md",
|
|
60
61
|
".githooks/prepare-commit-msg",
|
|
61
62
|
".githooks/wiki-commit-trailers.js",
|
|
@@ -63,28 +64,32 @@ exports.standardWikiFiles = new Set([
|
|
|
63
64
|
".codex/hooks/wiki-session-start.js",
|
|
64
65
|
".claude/settings.json",
|
|
65
66
|
".claude/hooks/wiki-session-start.js",
|
|
67
|
+
".cursor/rules/project-librarian.mdc",
|
|
68
|
+
".cursor/hooks.json",
|
|
69
|
+
".cursor/hooks/wiki-session-start.js",
|
|
70
|
+
".gemini/settings.json",
|
|
71
|
+
".gemini/hooks/wiki-session-start.js",
|
|
66
72
|
"wiki/README.md",
|
|
67
73
|
"wiki/startup.md",
|
|
68
74
|
"wiki/index.md",
|
|
69
75
|
"wiki/inbox/project-candidates.md",
|
|
70
76
|
"wiki/migration/inventory.md",
|
|
77
|
+
"wiki/migration/unit-map.md",
|
|
78
|
+
"wiki/migration/split-plan.md",
|
|
79
|
+
"wiki/migration/coverage.md",
|
|
71
80
|
"wiki/migration/plan.md",
|
|
72
81
|
"wiki/migration/review.md",
|
|
73
82
|
"wiki/migration/verification.md",
|
|
74
|
-
"wiki/
|
|
83
|
+
"wiki/migration/bulk-review.md",
|
|
75
84
|
"wiki/canonical/glossary.md",
|
|
76
|
-
"wiki/canonical/open-questions.md",
|
|
77
|
-
"wiki/canonical/assumptions.md",
|
|
78
|
-
"wiki/canonical/risks.md",
|
|
79
85
|
"wiki/canonical/migration-inbox.md",
|
|
80
86
|
"wiki/decisions/README.md",
|
|
81
87
|
"wiki/decisions/log.md",
|
|
82
88
|
"wiki/decisions/recent.md",
|
|
83
|
-
"wiki/decisions/decision-pack-template.md",
|
|
84
|
-
"wiki/decisions/full-adr-template.md",
|
|
85
89
|
"wiki/decisions/migration-inbox.md",
|
|
86
90
|
"wiki/meta/operating-model.md",
|
|
87
91
|
"wiki/meta/decision-policy.md",
|
|
92
|
+
"wiki/meta/document-taxonomy.md",
|
|
88
93
|
"wiki/meta/wiki-ops-v1-decisions.md",
|
|
89
94
|
"wiki/sources/karpathy-llm-wiki.md",
|
|
90
95
|
"wiki/sources/migration-inbox.md",
|
|
@@ -92,7 +97,7 @@ exports.standardWikiFiles = new Set([
|
|
|
92
97
|
"tools/project-librarian/agents/openai.yaml",
|
|
93
98
|
"tools/project-librarian/dist/init-project-wiki.js",
|
|
94
99
|
]);
|
|
95
|
-
exports.ignoredDirs = new Set([".git", ".codex", ".claude", "node_modules", ".next", "dist", "build", "coverage", "vendor", "tmp", "temp"]);
|
|
100
|
+
exports.ignoredDirs = new Set([".git", ".codex", ".claude", ".cursor", ".gemini", "node_modules", ".next", "dist", "build", "coverage", "vendor", "tmp", "temp"]);
|
|
96
101
|
function walkMarkdownFiles(dir = workspace_1.root, acc = [], baseDir = workspace_1.root) {
|
|
97
102
|
for (const entry of fs.readdirSync(dir, { withFileTypes: true })) {
|
|
98
103
|
const fullPath = path.join(dir, entry.name);
|
|
@@ -256,25 +261,6 @@ function stripMarkedSection(text, startMarker, endMarker) {
|
|
|
256
261
|
return text;
|
|
257
262
|
return `${text.slice(0, start).trimEnd()}\n\n${text.slice(end + endMarker.length).trimStart()}`.trim() + "\n";
|
|
258
263
|
}
|
|
259
|
-
function extractMarkedSection(text, startMarker, endMarker) {
|
|
260
|
-
const start = text.indexOf(startMarker);
|
|
261
|
-
const end = text.indexOf(endMarker);
|
|
262
|
-
if (start < 0 || end <= start)
|
|
263
|
-
return "";
|
|
264
|
-
return text.slice(start, end + endMarker.length).trim();
|
|
265
|
-
}
|
|
266
|
-
function withPreservedMarkedSections(relativePath, base, markerPairs) {
|
|
267
|
-
if (!(0, workspace_1.exists)(relativePath))
|
|
268
|
-
return base;
|
|
269
|
-
const current = (0, workspace_1.read)(relativePath);
|
|
270
|
-
const preserved = markerPairs
|
|
271
|
-
.map(([startMarker, endMarker]) => extractMarkedSection(current, startMarker, endMarker))
|
|
272
|
-
.filter(Boolean)
|
|
273
|
-
.filter((section) => !base.includes(section));
|
|
274
|
-
if (preserved.length === 0)
|
|
275
|
-
return base;
|
|
276
|
-
return `${base.trimEnd()}\n\n${preserved.join("\n\n")}\n`;
|
|
277
|
-
}
|
|
278
264
|
function hasGlossaryNeedSignal(text) {
|
|
279
265
|
return /(^|\n)##\s+(Glossary|Terms|Roles|Entities|Data Model|State Model|Permissions|Events|용어|역할|엔티티|상태 모델|권한|이벤트)(\s|$)|`[^`]+`\s*(term|role|state|permission|event|entity|API|DB|UI|용어|역할|상태|권한|이벤트|엔티티)/i.test(text);
|
|
280
266
|
}
|
|
@@ -282,6 +268,16 @@ function hasGlossaryTable(text) {
|
|
|
282
268
|
const body = (0, workspace_1.stripMetadataHeader)(text);
|
|
283
269
|
return /\|\s*Term\s*\|\s*Definition\s*\|\s*Avoid\s*\|\s*Related Canonical Doc\s*\|\s*Status\s*\|/.test(body);
|
|
284
270
|
}
|
|
271
|
+
// First "## TL;DR" bullet for answer-shaped query envelopes: gives an agent the
|
|
272
|
+
// page's one-line summary without opening the page. Pages without a TL;DR section
|
|
273
|
+
// return "" and the envelope simply omits the line — quality-check separately
|
|
274
|
+
// flags the missing TL;DR, so this is optional enrichment, not a fallback path.
|
|
275
|
+
function firstTldrBullet(text) {
|
|
276
|
+
const body = (0, workspace_1.stripMetadataHeader)(text);
|
|
277
|
+
const match = body.match(/^##\s+TL;DR[^\n]*\n([\s\S]*?)(?=\n##\s|(?![\s\S]))/m);
|
|
278
|
+
const bullet = match?.[1]?.split(/\r?\n/).find((line) => /^\s*-\s+\S/.test(line));
|
|
279
|
+
return bullet ? bullet.replace(/^\s*-\s*/, "").trim().slice(0, 160) : "";
|
|
280
|
+
}
|
|
285
281
|
function canonicalBodyForLint() {
|
|
286
282
|
return (0, workspace_1.walkFilesUnder)("wiki/canonical", (file) => /\.(md|mdx)$/i.test(file) && file !== "wiki/canonical/glossary.md")
|
|
287
283
|
.map((file) => (0, workspace_1.stripMetadataHeader)((0, workspace_1.read)(file)))
|
|
@@ -0,0 +1,141 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.wikiAnswerTruncationNotice = exports.wikiAnswerCharCap = exports.wikiRouterExemptPages = exports.wikiRouterDepthBudget = exports.wikiRouterRoot = void 0;
|
|
4
|
+
exports.finalizeWikiAnswer = finalizeWikiAnswer;
|
|
5
|
+
exports.buildWikiGraph = buildWikiGraph;
|
|
6
|
+
exports.wikiRouterDepths = wikiRouterDepths;
|
|
7
|
+
exports.wikiImpactAnswer = wikiImpactAnswer;
|
|
8
|
+
const wiki_files_1 = require("./wiki-files");
|
|
9
|
+
const workspace_1 = require("./workspace");
|
|
10
|
+
// Router reachability budget. The benchmark fixture A1 assert guarantees
|
|
11
|
+
// startup -> index -> answer page within two hops; real wikis add one hop for
|
|
12
|
+
// generated scoped routers (startup -> index -> wiki/indexes/auto-*.md -> page),
|
|
13
|
+
// so the real-wiki budget is three hops from wiki/startup.md.
|
|
14
|
+
exports.wikiRouterRoot = "wiki/startup.md";
|
|
15
|
+
exports.wikiRouterDepthBudget = 3;
|
|
16
|
+
// startup is the BFS root; README is a human entry document that is deliberately
|
|
17
|
+
// unrouted (the same exemption the orphan-page rule uses).
|
|
18
|
+
exports.wikiRouterExemptPages = new Set([exports.wikiRouterRoot, "wiki/README.md"]);
|
|
19
|
+
// Answer-shape discipline for wiki-side query/impact output: answer-first text,
|
|
20
|
+
// hard char cap, explicit truncation notice (never silent). Mirrors the MCP
|
|
21
|
+
// server constants (src/mcp-server.ts MAX_RESPONSE_CHARS / TRUNCATION_NOTICE);
|
|
22
|
+
// kept separate so the MCP server module and its node:sqlite loading path stay
|
|
23
|
+
// out of the bootstrap/diagnostics path.
|
|
24
|
+
exports.wikiAnswerCharCap = 4000;
|
|
25
|
+
exports.wikiAnswerTruncationNotice = "[truncated — refine the query]";
|
|
26
|
+
function finalizeWikiAnswer(body) {
|
|
27
|
+
if (body.length <= exports.wikiAnswerCharCap)
|
|
28
|
+
return body;
|
|
29
|
+
const budget = exports.wikiAnswerCharCap - exports.wikiAnswerTruncationNotice.length - 1;
|
|
30
|
+
return `${body.slice(0, budget > 0 ? budget : 0).trimEnd()}\n${exports.wikiAnswerTruncationNotice}`;
|
|
31
|
+
}
|
|
32
|
+
// decision_ref is frontmatter, not a wiki link, so the link extractor never sees
|
|
33
|
+
// it; normalize it here into a page edge. "none"/"-" are the documented empty
|
|
34
|
+
// markers in generated metadata headers.
|
|
35
|
+
function normalizedDecisionRef(file, value) {
|
|
36
|
+
const trimmed = value.trim();
|
|
37
|
+
if (!trimmed || trimmed === "none" || trimmed === "-")
|
|
38
|
+
return "";
|
|
39
|
+
return (0, wiki_files_1.normalizeWikiLinkTarget)(file, trimmed.replace(/^\[\[|\]\]$/g, ""));
|
|
40
|
+
}
|
|
41
|
+
function buildWikiGraph(pages) {
|
|
42
|
+
const files = new Set(pages.map((page) => page.file));
|
|
43
|
+
const links = [];
|
|
44
|
+
const incomingLinks = new Map();
|
|
45
|
+
const outgoingLinks = new Map();
|
|
46
|
+
const incomingDecisionRefs = new Map();
|
|
47
|
+
const outgoingDecisionRef = new Map();
|
|
48
|
+
for (const page of pages) {
|
|
49
|
+
for (const link of (0, wiki_files_1.extractWikiLinks)(page.file, page.text)) {
|
|
50
|
+
links.push(link);
|
|
51
|
+
outgoingLinks.set(page.file, [...(outgoingLinks.get(page.file) ?? []), link]);
|
|
52
|
+
incomingLinks.set(link.normalizedTarget, [...(incomingLinks.get(link.normalizedTarget) ?? []), link]);
|
|
53
|
+
}
|
|
54
|
+
const ref = normalizedDecisionRef(page.file, (0, workspace_1.metadataValue)(page.text, "decision_ref"));
|
|
55
|
+
if (ref && files.has(ref)) {
|
|
56
|
+
outgoingDecisionRef.set(page.file, ref);
|
|
57
|
+
incomingDecisionRefs.set(ref, [...(incomingDecisionRefs.get(ref) ?? []), page.file]);
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
return { files, links, incomingLinks, outgoingLinks, incomingDecisionRefs, outgoingDecisionRef };
|
|
61
|
+
}
|
|
62
|
+
// BFS depths over existing pages from wiki/startup.md (depth 0). Pages absent
|
|
63
|
+
// from the result are unreachable through the router chain. Only links whose
|
|
64
|
+
// target exists are traversed; broken links are the broken-link rule's job.
|
|
65
|
+
function wikiRouterDepths(graph) {
|
|
66
|
+
const depths = new Map();
|
|
67
|
+
if (!graph.files.has(exports.wikiRouterRoot))
|
|
68
|
+
return depths;
|
|
69
|
+
depths.set(exports.wikiRouterRoot, 0);
|
|
70
|
+
const queue = [exports.wikiRouterRoot];
|
|
71
|
+
while (queue.length > 0) {
|
|
72
|
+
const current = queue.shift();
|
|
73
|
+
const depth = depths.get(current) ?? 0;
|
|
74
|
+
for (const link of graph.outgoingLinks.get(current) ?? []) {
|
|
75
|
+
const target = link.normalizedTarget;
|
|
76
|
+
if (!graph.files.has(target) || depths.has(target))
|
|
77
|
+
continue;
|
|
78
|
+
depths.set(target, depth + 1);
|
|
79
|
+
queue.push(target);
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
return depths;
|
|
83
|
+
}
|
|
84
|
+
// Wiki impact: the --code-impact envelope shape applied to wiki maintenance.
|
|
85
|
+
// Given a page or term, report which pages link to it (review candidates when it
|
|
86
|
+
// changes), which pages cite it as decision_ref, what it depends on, and how the
|
|
87
|
+
// router reaches it. Bounded by sampling plus the shared answer cap.
|
|
88
|
+
const impactMatchCap = 5;
|
|
89
|
+
const impactListCap = 12;
|
|
90
|
+
function sampled(items, cap) {
|
|
91
|
+
if (items.length === 0)
|
|
92
|
+
return "none";
|
|
93
|
+
const shown = items.slice(0, cap).join(", ");
|
|
94
|
+
return items.length > cap ? `${shown}, …+${items.length - cap} more` : shown;
|
|
95
|
+
}
|
|
96
|
+
function uniqueSorted(values) {
|
|
97
|
+
return Array.from(new Set(values)).sort();
|
|
98
|
+
}
|
|
99
|
+
function plural(count, noun) {
|
|
100
|
+
return `${count} ${noun}${count === 1 ? "" : "s"}`;
|
|
101
|
+
}
|
|
102
|
+
function wikiImpactAnswer(pages, term) {
|
|
103
|
+
const graph = buildWikiGraph(pages);
|
|
104
|
+
const depths = wikiRouterDepths(graph);
|
|
105
|
+
const textByFile = new Map(pages.map((page) => [page.file, page.text]));
|
|
106
|
+
const lowered = term.toLowerCase();
|
|
107
|
+
const exactTarget = (0, wiki_files_1.normalizeWikiLinkTarget)("wiki/index.md", term.replace(/^\[\[|\]\]$/g, ""));
|
|
108
|
+
const matches = pages
|
|
109
|
+
.map((page) => ({ file: page.file, title: (0, wiki_files_1.wikiTitleForFile)(page.file, page.text) }))
|
|
110
|
+
.filter((page) => page.file === exactTarget || page.file.toLowerCase().includes(lowered) || page.title.toLowerCase().includes(lowered))
|
|
111
|
+
.sort((a, b) => Number(b.file === exactTarget) - Number(a.file === exactTarget) || a.file.localeCompare(b.file));
|
|
112
|
+
if (matches.length === 0)
|
|
113
|
+
return `Wiki impact "${term}": no matching wiki pages.`;
|
|
114
|
+
const shown = matches.slice(0, impactMatchCap);
|
|
115
|
+
// Headline counts are unions across the shown matches, not sums: a page that
|
|
116
|
+
// links two matched targets is one review candidate, not two.
|
|
117
|
+
const incomingUnion = new Set(shown.flatMap((match) => (graph.incomingLinks.get(match.file) ?? []).map((link) => link.file)));
|
|
118
|
+
const refUnion = new Set(shown.flatMap((match) => graph.incomingDecisionRefs.get(match.file) ?? []));
|
|
119
|
+
const lines = [
|
|
120
|
+
`Wiki impact "${term}": ${plural(matches.length, "matching page")}${matches.length > impactMatchCap ? ` (top ${impactMatchCap} shown)` : ""}; review the ${plural(incomingUnion.size, "linking page")} and ${plural(refUnion.size, "decision_ref citation")} below when ${matches.length === 1 ? "this page changes" : "these pages change"}.`,
|
|
121
|
+
];
|
|
122
|
+
for (const match of shown) {
|
|
123
|
+
const text = textByFile.get(match.file) ?? "";
|
|
124
|
+
const incoming = uniqueSorted((graph.incomingLinks.get(match.file) ?? []).map((link) => link.file));
|
|
125
|
+
const refs = uniqueSorted(graph.incomingDecisionRefs.get(match.file) ?? []);
|
|
126
|
+
const outgoing = uniqueSorted((graph.outgoingLinks.get(match.file) ?? []).map((link) => link.normalizedTarget));
|
|
127
|
+
const depth = depths.get(match.file);
|
|
128
|
+
const trigger = (0, workspace_1.metadataValue)(text, "review_trigger");
|
|
129
|
+
lines.push("");
|
|
130
|
+
lines.push(`${match.file} — ${match.title}`);
|
|
131
|
+
if (trigger)
|
|
132
|
+
lines.push(` review_trigger: ${trigger}`);
|
|
133
|
+
lines.push(` incoming links (${incoming.length}): ${sampled(incoming, impactListCap)}`);
|
|
134
|
+
lines.push(` decision_ref from (${refs.length}): ${sampled(refs, impactListCap)}`);
|
|
135
|
+
lines.push(` outgoing links (${outgoing.length}): ${sampled(outgoing, impactListCap)}`);
|
|
136
|
+
lines.push(depth === undefined
|
|
137
|
+
? ` router: unreachable from ${exports.wikiRouterRoot}`
|
|
138
|
+
: ` router: reachable at depth ${depth} (budget ${exports.wikiRouterDepthBudget})`);
|
|
139
|
+
}
|
|
140
|
+
return finalizeWikiAnswer(lines.join("\n"));
|
|
141
|
+
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "project-librarian",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.3.0",
|
|
4
4
|
"description": "Create and maintain compact project context for humans and LLM coding agents.",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"type": "commonjs",
|
|
@@ -8,6 +8,8 @@
|
|
|
8
8
|
"agents",
|
|
9
9
|
"claude-code",
|
|
10
10
|
"codex",
|
|
11
|
+
"cursor",
|
|
12
|
+
"gemini",
|
|
11
13
|
"llm",
|
|
12
14
|
"project-planning",
|
|
13
15
|
"wiki"
|
|
@@ -26,8 +28,6 @@
|
|
|
26
28
|
"LICENSE",
|
|
27
29
|
"README.md",
|
|
28
30
|
"README.ko.md",
|
|
29
|
-
"README.ja.md",
|
|
30
|
-
"README.zh.md",
|
|
31
31
|
"SKILL.md"
|
|
32
32
|
],
|
|
33
33
|
"bin": {
|
|
@@ -37,12 +37,15 @@
|
|
|
37
37
|
"node": ">=22.13"
|
|
38
38
|
},
|
|
39
39
|
"scripts": {
|
|
40
|
-
"benchmark": "
|
|
41
|
-
"benchmark:baseline": "npm run build && node benchmarks/project-metrics.js --sample-repo benchmarks/samples/web-service --sample-repo benchmarks/samples/python-cli --sample-repo benchmarks/samples/mixed-monorepo --require-clean --save-baseline --markdown",
|
|
40
|
+
"benchmark": "npm run benchmark:llm:dry-run",
|
|
42
41
|
"benchmark:ci": "npm run benchmark:ci-smoke",
|
|
43
|
-
"benchmark:ci-smoke": "npm run
|
|
44
|
-
"benchmark:
|
|
45
|
-
"benchmark:
|
|
42
|
+
"benchmark:ci-smoke": "npm run benchmark:llm:parse-smoke && npm run benchmark:llm:dry-run",
|
|
43
|
+
"benchmark:llm": "npm run build && node benchmarks/codex-llm-metrics.js",
|
|
44
|
+
"benchmark:llm:dry-run": "npm run build && node benchmarks/codex-llm-metrics.js --dry-run",
|
|
45
|
+
"benchmark:llm:parse-smoke": "node tests/validators/codex-llm-benchmark-smoke.js",
|
|
46
|
+
"benchmark:injection-sentinel": "node benchmarks/tools/injection-sentinel.js",
|
|
47
|
+
"benchmark:real-corpus:demo": "npm run build && node benchmarks/tools/real-corpus-offline-demo.js",
|
|
48
|
+
"benchmark:release": "npm run benchmark:llm -- --full-matrix --runs 3 --warmup-runs 1 --min-runs-for-claim 3 --require-clean --require-claimable --out benchmarks/reports/llm/current.json --markdown benchmarks/reports/llm/current.md",
|
|
46
49
|
"build": "tsc && chmod +x dist/init-project-wiki.js",
|
|
47
50
|
"typecheck": "tsc --noEmit",
|
|
48
51
|
"unit": "node --test tests/unit/*.test.js",
|
package/README.ja.md
DELETED
|
@@ -1,227 +0,0 @@
|
|
|
1
|
-
# Project Librarian
|
|
2
|
-
|
|
3
|
-
[](https://www.npmjs.com/package/project-librarian)
|
|
4
|
-
[](https://opensource.org/licenses/MIT)
|
|
5
|
-
[](https://nodejs.org/)
|
|
6
|
-
[](https://nodejs.org/api/sqlite.html)
|
|
7
|
-
|
|
8
|
-
Codex と Claude Code のための簡潔なプロジェクトメモリとコード根拠。
|
|
9
|
-
|
|
10
|
-
Project Librarian は、リポジトリローカルの計画 wiki、簡潔な起動 hook、任意の SQLite コード根拠 index を作成します。エージェントはプロジェクト計画から開始し、必要な文書へルーティングされ、リポジトリ全体を繰り返しスキャンせずにコードに基づく根拠を確認できます。
|
|
11
|
-
|
|
12
|
-
言語: [English](README.md) | [한국어](README.ko.md) | [日本語](README.ja.md) | [简体中文](README.zh.md)
|
|
13
|
-
|
|
14
|
-
## 存在理由
|
|
15
|
-
|
|
16
|
-
LLM コーディングエージェントは、セッションごとにプロジェクトを再発見するためにコンテキストとツール呼び出しを消費しがちです。古い会話の読み取り、Markdown のスキャン、ソース検索、関連ファイルの推測が繰り返されます。
|
|
17
|
-
|
|
18
|
-
Project Librarian はエージェントに 2 つのローカル正本を提供します。
|
|
19
|
-
|
|
20
|
-
| 表面 | エージェントが得るもの |
|
|
21
|
-
| --- | --- |
|
|
22
|
-
| `wiki/startup.md` + `wiki/index.md` | 短いセッション開始要約とルーター。必要な計画ページだけを読みます。 |
|
|
23
|
-
| `wiki/canonical/` および `wiki/decisions/` | 現在のプロジェクト事実、制約、リスク、パッケージ契約、CLI 動作、持続的な意思決定。 |
|
|
24
|
-
| `.codex/` および `.claude/` hook | 全 wiki をロードしない Codex/Claude Code 起動コンテキスト。 |
|
|
25
|
-
| `.project-wiki/code-evidence.sqlite` | ファイル、シンボル、import、route、所有者、ワークスペースグラフ、レポート、影響確認のための再生成可能なコード根拠。 |
|
|
26
|
-
| 診断とマイグレーションモード | リンク確認、品質確認、マイグレーション受信箱、古い信号のレポート、作業フローの問題発見時の issue draft。 |
|
|
27
|
-
|
|
28
|
-
重要なのは「文書を増やすこと」ではありません。最初のエージェント読み取り量を小さく保ち、より深いプロジェクト正本とコード根拠への信頼できる経路を与えることです。
|
|
29
|
-
|
|
30
|
-
## ベンチマーク結果
|
|
31
|
-
|
|
32
|
-
ベンチマークはメンテナー向けリリース根拠であり、公開ユーザー向け作業フローではありません。README とリリースノートが曖昧な性能表現ではなく、境界付きの数値で説明できるようにする根拠です。
|
|
33
|
-
|
|
34
|
-
最新 clean 大規模レポート: `benchmarks/reports/current-large.json`、2026-06-09T08:08:07.238Z 生成、Node v22.19.0、darwin arm64、Apple M4 Pro、commit `18e730882c4f`、測定実行 5 回と破棄したウォームアップ実行 1 回。時間測定状態は `stable`、unstable metrics は `none`、git 状態の指紋は clean です。
|
|
35
|
-
|
|
36
|
-
| 項目 | 結果 |
|
|
37
|
-
| --- | ---: |
|
|
38
|
-
| Markdown コンテキスト推定回避量の中央値 | 99.61% |
|
|
39
|
-
| Markdown コンテキスト推定回避量の最小値 | 99.43% |
|
|
40
|
-
| 読み取り時間削減の中央値 | 99.47% |
|
|
41
|
-
| 読み取り時間削減の最小値 | 99.26% |
|
|
42
|
-
| 測定した wiki ページ | 1,601 |
|
|
43
|
-
| コード index ファイル | 1,608 |
|
|
44
|
-
| コード index 時間 | 336.312ms |
|
|
45
|
-
| コード index スループット | 4,781.27 files/sec |
|
|
46
|
-
| 増分 index 時間 | 186.776ms |
|
|
47
|
-
| 全体に対する増分時間削減 | 45.52% |
|
|
48
|
-
| アーキテクチャレポート時間 | 251.175ms |
|
|
49
|
-
| アーキテクチャレポート根拠テーブル | 6 |
|
|
50
|
-
| アーキテクチャレポート route | 24 |
|
|
51
|
-
| サンプルリポジトリ | 3 |
|
|
52
|
-
| ベンチマーク実行 | 5 |
|
|
53
|
-
| ウォームアップ実行 | 1 |
|
|
54
|
-
| 時間測定状態 | stable |
|
|
55
|
-
| 不安定な測定値 | none |
|
|
56
|
-
|
|
57
|
-
シナリオ要約:
|
|
58
|
-
|
|
59
|
-
| シナリオ | 規模 | 結果 |
|
|
60
|
-
| --- | ---: | --- |
|
|
61
|
-
| 文書の多い wiki | 500ページ | 99.74% Markdown コンテキスト推定回避、99.47% 読み取り削減、43.83ms query |
|
|
62
|
-
| モノレポ wiki | 320ページ | 99.43% Markdown コンテキスト推定回避、99.26% 読み取り削減、81.12ms doctor |
|
|
63
|
-
| スコープ別ルーター wiki | 720ページ | 99.61% Markdown コンテキスト推定回避、99.55% 読み取り削減、67.684ms refresh |
|
|
64
|
-
| コードの多い混合 index | 1,608ファイル | 336.312ms full index、186.776ms incremental、251.175ms report、626.969ms Tree-sitter index |
|
|
65
|
-
| サンプルリポジトリ検証 | 3リポジトリ、16ファイル | 132.363ms コード index 中央値、135.694ms アーキテクチャレポート中央値 |
|
|
66
|
-
|
|
67
|
-
主張範囲: トークン推定値は `ceil(characters / 4)` による Markdown コンテキストサイズ推定です。モデル tokenizer 出力や API 課金カウンターではなく、実際の LLM トークン使用量を測定していません。ベンチマークは、targeted retrieval で読む wiki コンテキストが、fixture の全 wiki Markdown ファイルを読む naive full-wiki scan に比べてどれだけ Markdown コンテキスト入力を避けるかを比較します。コード index 測定値は生成/サンプルリポジトリで測定したローカル CLI 子プロセス時間です。
|
|
68
|
-
|
|
69
|
-
## インストール
|
|
70
|
-
|
|
71
|
-
初期 skill install にだけ `npx` を使います。
|
|
72
|
-
|
|
73
|
-
```bash
|
|
74
|
-
npx project-librarian install-skill --scope user --agents both
|
|
75
|
-
```
|
|
76
|
-
|
|
77
|
-
現在のリポジトリにインストール:
|
|
78
|
-
|
|
79
|
-
```bash
|
|
80
|
-
npx project-librarian install-skill --scope project --agents both
|
|
81
|
-
```
|
|
82
|
-
|
|
83
|
-
`install-skill` は再利用可能な skill ファイルだけをコピーします。`AGENTS.md`、`CLAUDE.md`、`wiki/`、`.codex/hooks.json`、`.claude/settings.json` は作成または更新しません。
|
|
84
|
-
|
|
85
|
-
| 状況 | コマンド |
|
|
86
|
-
| --- | --- |
|
|
87
|
-
| Codex と Claude Code にグローバルインストール | `npx project-librarian install-skill --scope user --agents both` |
|
|
88
|
-
| 現在のリポジトリにインストール | `npx project-librarian install-skill --scope project --agents both` |
|
|
89
|
-
| Codex のみ | `npx project-librarian install-skill --agents codex` |
|
|
90
|
-
| Claude Code のみ | `npx project-librarian install-skill --agents claude` |
|
|
91
|
-
| インストール結果をプレビュー | `npx project-librarian install-skill --scope project --agents both --dry-run` |
|
|
92
|
-
|
|
93
|
-
## エージェント実行経路
|
|
94
|
-
|
|
95
|
-
インストール後、エージェントは `npx` ではなく、インストール済みのローカルコピーを `node` で実行してください。これにより、制限されたエージェント環境でネットワークアクセスと固定されていないパッケージ実行を避けられます。
|
|
96
|
-
|
|
97
|
-
| インストール先 | 実行経路 |
|
|
98
|
-
| --- | --- |
|
|
99
|
-
| プロジェクト範囲 Codex skill | `node .codex/skills/project-librarian/dist/init-project-wiki.js` |
|
|
100
|
-
| プロジェクト範囲 Claude skill | `node .claude/skills/project-librarian/dist/init-project-wiki.js` |
|
|
101
|
-
| ユーザー範囲 Codex skill | `node ~/.codex/skills/project-librarian/dist/init-project-wiki.js` |
|
|
102
|
-
| ユーザー範囲 Claude skill | `node ~/.claude/skills/project-librarian/dist/init-project-wiki.js` |
|
|
103
|
-
|
|
104
|
-
以下の例では次を使います。
|
|
105
|
-
|
|
106
|
-
```bash
|
|
107
|
-
PROJECT_LIBRARIAN="node .codex/skills/project-librarian/dist/init-project-wiki.js"
|
|
108
|
-
```
|
|
109
|
-
|
|
110
|
-
インストール先に合うローカル実行経路を使ってください。
|
|
111
|
-
|
|
112
|
-
## 一般的なエージェント作業
|
|
113
|
-
|
|
114
|
-
プロジェクトルートで wiki を作成または更新します。
|
|
115
|
-
|
|
116
|
-
```bash
|
|
117
|
-
$PROJECT_LIBRARIAN
|
|
118
|
-
```
|
|
119
|
-
|
|
120
|
-
Wiki の検証と保守:
|
|
121
|
-
|
|
122
|
-
| 目的 | エージェントコマンド |
|
|
123
|
-
| --- | --- |
|
|
124
|
-
| wiki 作成または更新 | `$PROJECT_LIBRARIAN` |
|
|
125
|
-
| 既存 docs/wiki のマイグレーション | `$PROJECT_LIBRARIAN --migrate` |
|
|
126
|
-
| 生成された設定の検証 | `$PROJECT_LIBRARIAN --lint` |
|
|
127
|
-
| リンクと文書品質の確認 | `$PROJECT_LIBRARIAN --doctor` |
|
|
128
|
-
| 診断前に生成 routing を更新 | `$PROJECT_LIBRARIAN --doctor --fix` |
|
|
129
|
-
| project wiki 検索 | `$PROJECT_LIBRARIAN --query "authentication decisions"` |
|
|
130
|
-
| 候補メモの保存 | `$PROJECT_LIBRARIAN --capture-inbox --title "Candidate" --content "Details"` |
|
|
131
|
-
| 古い、または未解決の wiki ページの報告 | `$PROJECT_LIBRARIAN --prune-check` |
|
|
132
|
-
| git config を変えず hook ファイルをインストール | `$PROJECT_LIBRARIAN --no-git-config` |
|
|
133
|
-
|
|
134
|
-
コード根拠:
|
|
135
|
-
|
|
136
|
-
| 目的 | エージェントコマンド |
|
|
137
|
-
| --- | --- |
|
|
138
|
-
| 既定の根拠 cache 作成 | `$PROJECT_LIBRARIAN --code-index --code-scope src` |
|
|
139
|
-
| 複数 scope の build | `$PROJECT_LIBRARIAN --code-index --code-scope src --code-scope packages/api` |
|
|
140
|
-
| 増分更新を要求 | `$PROJECT_LIBRARIAN --code-index --incremental` |
|
|
141
|
-
| full rebuild を強制 | `$PROJECT_LIBRARIAN --code-index --code-index-full` |
|
|
142
|
-
| 任意の Tree-sitter backend を使用 | `$PROJECT_LIBRARIAN --code-index --code-parser tree-sitter` |
|
|
143
|
-
| cache 状態を表示 | `$PROJECT_LIBRARIAN --code-status` |
|
|
144
|
-
| index 済みファイル一覧 | `$PROJECT_LIBRARIAN --code-files` |
|
|
145
|
-
| アーキテクチャ/所有者レポートを出力 | `$PROJECT_LIBRARIAN --code-report` |
|
|
146
|
-
| レポート section だけ出力 | `$PROJECT_LIBRARIAN --code-report --code-report-section routes` |
|
|
147
|
-
| 影響根拠を確認 | `$PROJECT_LIBRARIAN --code-impact healthHandler` |
|
|
148
|
-
| index 済みシンボルを検索 | `$PROJECT_LIBRARIAN --code-search-symbol Auth` |
|
|
149
|
-
| 保守的な読み取り専用 SQL を実行 | `$PROJECT_LIBRARIAN --code-query "select path from files order by path"` |
|
|
150
|
-
|
|
151
|
-
コード根拠モードは一度に 1 つだけ実行できます。`--incremental`、`--code-index-full`、`--code-parser` は `--code-index` と一緒に使う場合のみ有効です。
|
|
152
|
-
|
|
153
|
-
## インストールされるファイル
|
|
154
|
-
|
|
155
|
-
- `AGENTS.md`
|
|
156
|
-
- `CLAUDE.md`
|
|
157
|
-
- `wiki/AGENTS.md`
|
|
158
|
-
- `.codex/hooks.json`
|
|
159
|
-
- `.codex/hooks/wiki-session-start.js`
|
|
160
|
-
- `.claude/settings.json`
|
|
161
|
-
- `.claude/hooks/wiki-session-start.js`
|
|
162
|
-
- `.githooks/prepare-commit-msg`
|
|
163
|
-
- `.githooks/wiki-commit-trailers.js`
|
|
164
|
-
- `wiki/canonical/`, `wiki/decisions/`, `wiki/inbox/`, `wiki/meta/`, `wiki/sources/`, `wiki/migration/`
|
|
165
|
-
- 破棄可能なコード根拠 cache としての `.project-wiki/code-evidence.sqlite`
|
|
166
|
-
|
|
167
|
-
## 仕組み
|
|
168
|
-
|
|
169
|
-
1. Bootstrap は保存優先の wiki 構造と、marker で境界付けられたエージェント指示 section を作成します。
|
|
170
|
-
2. セッション開始 hook は文字数予算付きの `wiki/startup.md` と `wiki/index.md` だけを注入します。
|
|
171
|
-
3. 詳細な計画正本は canonical、decision、source、meta page にあり、エージェントが必要なときに読みます。
|
|
172
|
-
4. `--refresh-index` は新しい wiki page をルーティングし、route が多い場合は `wiki/indexes/auto-*.md` スコープ別ルーターに分割します。
|
|
173
|
-
5. `--code-index` は `.project-wiki/` 配下に破棄可能な SQLite 根拠 cache を作ります。
|
|
174
|
-
6. `--code-report`、`--code-impact`、`--code-search-symbol`、`--code-query` が計画更新用のコード根拠を提供します。
|
|
175
|
-
7. 診断は壊れたリンク、重複 route、orphan page、古いページ、欠落した TL;DR、根拠 gap、マイグレーションコピーリスクを報告します。
|
|
176
|
-
|
|
177
|
-
マイグレーションはレビュー優先です。`--migrate` は既存 `wiki/` を `wiki_legacy*` として保存し、migration inbox を作成し、legacy Markdown を新しい canonical truth に直接コピーしません。
|
|
178
|
-
|
|
179
|
-
## 言語サポート表
|
|
180
|
-
|
|
181
|
-
| 言語 | 拡張子 | 既定の抽出 | Tree-sitter 抽出 | index される根拠 |
|
|
182
|
-
| --- | --- | --- | --- | --- |
|
|
183
|
-
| TypeScript | `.ts`, `.tsx`, `.cts`, `.mts` | `typescript-ast` | `tree-sitter-typescript`, `tree-sitter-tsx` | 関数、クラス、メソッド、変数、interface、type、enum、import、export、呼び出し、一般的な HTTP route |
|
|
184
|
-
| JavaScript | `.js`, `.jsx`, `.cjs`, `.mjs` | `typescript-ast` | `tree-sitter-javascript` | 関数、クラス、メソッド、変数、import、export、`require()` 呼び出し、一般的な HTTP route |
|
|
185
|
-
| Python | `.py` | `python-light` | `tree-sitter-python` | 関数、クラス、`import`、`from ... import` |
|
|
186
|
-
| Go | `.go` | `go-light` | `tree-sitter-go` | 関数、メソッド、型、const、var、単一 import、import block |
|
|
187
|
-
| Rust | `.rs` | 一覧のみ | `tree-sitter-rust` | 関数、struct、enum、trait、impl、`use` import |
|
|
188
|
-
| Java | `.java` | 一覧のみ | `tree-sitter-java` | クラス、interface、enum、メソッド、import |
|
|
189
|
-
| PHP | `.php` | 一覧のみ | `tree-sitter-php` | 関数、クラス、interface、trait、メソッド、namespace use |
|
|
190
|
-
| Kotlin | `.kt`, `.kts` | 一覧のみ | `tree-sitter-kotlin` | 関数、クラス、object、import |
|
|
191
|
-
| Swift | `.swift` | 一覧のみ | `tree-sitter-swift` | 関数、クラス、struct、protocol、enum、import |
|
|
192
|
-
| C | `.c`, `.h` | 一覧のみ | `tree-sitter-c` | 関数、struct、enum、include |
|
|
193
|
-
| C++ | `.cc`, `.cpp`, `.cxx`, `.hpp`, `.hh`, `.hxx` | 一覧のみ | `tree-sitter-cpp` | 関数、class/struct、namespace、enum、include/using |
|
|
194
|
-
| C# | `.cs` | 一覧のみ | `tree-sitter-csharp` | class、interface、struct、enum、メソッド、using |
|
|
195
|
-
|
|
196
|
-
`.rb`、`.vue`、`.css` は認識されますが一覧のみです。設定ファイルは設定根拠または一覧根拠として index されます。
|
|
197
|
-
|
|
198
|
-
## CLI リファレンス
|
|
199
|
-
|
|
200
|
-
エージェント実行にはローカル実行経路を使います。
|
|
201
|
-
|
|
202
|
-
```bash
|
|
203
|
-
$PROJECT_LIBRARIAN [init] [options]
|
|
204
|
-
$PROJECT_LIBRARIAN install-skill [--scope user|project] [--agents codex|claude|both]
|
|
205
|
-
```
|
|
206
|
-
|
|
207
|
-
重要なオプション: `--migrate`, `--lint`, `--link-check`, `--quality-check`, `--doctor`, `--doctor --fix`, `--query`, `--refresh-index`, `--capture-inbox`, `--issue-draft`, `--issue-create`, `--glossary-init`, `--prune-check`, `--review-migration`, `--no-git-config`, `--code-index`, `--code-report`, `--code-impact`, `--code-search-symbol`, `--code-query`.
|
|
208
|
-
|
|
209
|
-
## 開発
|
|
210
|
-
|
|
211
|
-
```bash
|
|
212
|
-
npm install
|
|
213
|
-
npm run typecheck
|
|
214
|
-
npm run build
|
|
215
|
-
npm test
|
|
216
|
-
npm pack --dry-run
|
|
217
|
-
```
|
|
218
|
-
|
|
219
|
-
メンテナー向けベンチマークコマンドは [benchmarks/README.md](benchmarks/README.md) にあります。これはリリース根拠と公開主張の検証用であり、通常のエンドユーザー設定ではありません。
|
|
220
|
-
|
|
221
|
-
## 着想
|
|
222
|
-
|
|
223
|
-
このプロジェクトは Andrej Karpathy の [LLM Wiki](https://gist.github.com/karpathy/442a6bf555914893e9891c11519de94f) パターンに影響を受けています。
|
|
224
|
-
|
|
225
|
-
## ライセンス
|
|
226
|
-
|
|
227
|
-
MIT
|