project-librarian 0.3.0 → 0.4.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.ko.md +42 -11
- package/README.md +39 -9
- package/SKILL.md +1 -0
- package/dist/args.js +20 -3
- package/dist/code-index/evidence.js +79 -0
- package/dist/code-index/extractors/config.js +58 -0
- package/dist/code-index/extractors/light-languages.js +52 -0
- package/dist/code-index/extractors/registry.js +137 -0
- package/dist/code-index/extractors/shared.js +36 -0
- package/dist/code-index/extractors/tree-sitter.js +319 -0
- package/dist/code-index/extractors/types.js +2 -0
- package/dist/code-index/extractors/typescript.js +211 -0
- package/dist/code-index/incremental.js +16 -0
- package/dist/code-index/index-health.js +164 -0
- package/dist/code-index/modes.js +303 -0
- package/dist/code-index/ownership.js +183 -0
- package/dist/code-index/reports.js +322 -0
- package/dist/code-index/schema.js +196 -0
- package/dist/code-index/search.js +153 -0
- package/dist/code-index-file-policy.js +21 -27
- package/dist/code-index.js +224 -1657
- package/dist/init-project-wiki.js +65 -37
- package/dist/mcp-server.js +325 -11
- package/dist/migration.js +10 -6
- package/dist/modes.js +139 -54
- package/dist/path-ignore-policy.js +30 -0
- package/dist/retrieval-eval.js +68 -0
- package/dist/wiki-concepts.js +49 -0
- package/dist/wiki-corpus.js +25 -0
- package/dist/wiki-diagnostics.js +19 -0
- package/dist/wiki-files.js +107 -1
- package/dist/wiki-graph.js +26 -2
- package/dist/wiki-visualizer.js +558 -0
- package/package.json +11 -4
|
@@ -7,13 +7,14 @@ const install_skill_1 = require("./install-skill");
|
|
|
7
7
|
const modes_1 = require("./modes");
|
|
8
8
|
const migration_1 = require("./migration");
|
|
9
9
|
const templates_1 = require("./templates");
|
|
10
|
+
const wiki_visualizer_1 = require("./wiki-visualizer");
|
|
10
11
|
const workspace_1 = require("./workspace");
|
|
11
12
|
function codeIndex() {
|
|
12
13
|
return require("./code-index");
|
|
13
14
|
}
|
|
14
15
|
function printUsage() {
|
|
15
16
|
console.log(`Usage:
|
|
16
|
-
project-librarian [init] [options]
|
|
17
|
+
project-librarian [init|update] [options]
|
|
17
18
|
project-librarian install-skill [--scope user|project] [--agents codex|claude|cursor|gemini|all]
|
|
18
19
|
project-librarian mcp
|
|
19
20
|
|
|
@@ -33,6 +34,8 @@ Options:
|
|
|
33
34
|
--issue-title <title> Override the generated issue draft title.
|
|
34
35
|
--query <terms> Search wiki paths, metadata, titles, and bodies (answer-shaped, capped output).
|
|
35
36
|
--wiki-impact <page-or-term> Show wiki backlinks, decision_ref citations, and router depth for matching pages.
|
|
37
|
+
--wiki-visualize Write a static wiki graph visualizer to .project-wiki/wiki-graph.html.
|
|
38
|
+
--wiki-visualize-out <path> With --wiki-visualize, write under a custom .project-wiki/ path.
|
|
36
39
|
--refresh-index Update the managed auto-discovered wiki index block.
|
|
37
40
|
--capture-inbox Append a candidate note with --title, --content, and optional --category.
|
|
38
41
|
--glossary-init Create and route the optional glossary page.
|
|
@@ -40,6 +43,7 @@ Options:
|
|
|
40
43
|
--review-migration Sync unit coverage and compatible inbox statuses into migration review files.
|
|
41
44
|
--no-git-config Install hook files without changing git core.hooksPath.
|
|
42
45
|
--code-index Build the disposable .project-wiki code evidence index.
|
|
46
|
+
--code-index-health Inspect code evidence cache compatibility and print rebuild guidance without writing.
|
|
43
47
|
--acknowledge-small-repo With --code-index, proceed below the small-repo scale gate after its cost warning.
|
|
44
48
|
--incremental With --code-index, require an existing compatible index and update only changes.
|
|
45
49
|
--code-index-full With --code-index, force a full rebuild even when incremental update is possible.
|
|
@@ -49,10 +53,12 @@ Options:
|
|
|
49
53
|
--code-report Print architecture and ownership summaries from the code evidence index.
|
|
50
54
|
--code-report-section <section> With --code-report, print one section: coverage, ownership, languages, parsers, workspaces, workspace-graph, routes, hotspots, configs, or edges.
|
|
51
55
|
--code-impact <term> Show file, symbol, route, import, and edge impact evidence for a term.
|
|
56
|
+
--code-context-pack <term> Print a budgeted first-pass code context pack for a path, symbol, route, or module term.
|
|
52
57
|
--code-search-symbol <term> Search indexed symbols.
|
|
53
58
|
|
|
54
59
|
Commands:
|
|
55
|
-
|
|
60
|
+
update Run the idempotent wiki/setup update path; rejects migration flags.
|
|
61
|
+
mcp Run the stdio MCP server exposing answer-shaped code-evidence tools (code_context_pack, code_impact, code_ownership, code_workspace_graph, code_search, code_status) over the existing .project-wiki index.
|
|
56
62
|
|
|
57
63
|
--help Show this help.`);
|
|
58
64
|
}
|
|
@@ -63,6 +69,20 @@ Commands:
|
|
|
63
69
|
function exitAfterStdoutDrain(code) {
|
|
64
70
|
process.stdout.write("", () => process.exit(code));
|
|
65
71
|
}
|
|
72
|
+
function activeCodeEvidenceCliModes() {
|
|
73
|
+
const modes = [
|
|
74
|
+
{ active: args_1.codeQueryMode, drainStdout: true, run: (codeIndexModule) => codeIndexModule.runCodeQueryMode() },
|
|
75
|
+
{ active: args_1.codeReportMode, drainStdout: true, run: (codeIndexModule) => codeIndexModule.runCodeReportMode() },
|
|
76
|
+
{ active: args_1.codeStatusMode, drainStdout: true, run: (codeIndexModule) => codeIndexModule.runCodeStatusMode() },
|
|
77
|
+
{ active: args_1.codeFilesMode, drainStdout: true, run: (codeIndexModule) => codeIndexModule.runCodeFilesMode() },
|
|
78
|
+
{ active: args_1.codeImpactMode, drainStdout: true, run: (codeIndexModule) => codeIndexModule.runCodeImpactMode() },
|
|
79
|
+
{ active: args_1.codeContextPackMode, drainStdout: true, run: (codeIndexModule) => codeIndexModule.runCodeContextPackMode() },
|
|
80
|
+
{ active: args_1.codeSearchSymbolMode, drainStdout: true, run: (codeIndexModule) => codeIndexModule.runCodeSearchSymbolMode() },
|
|
81
|
+
{ active: args_1.codeIndexHealthMode, drainStdout: true, run: (codeIndexModule) => codeIndexModule.runCodeIndexHealthMode() },
|
|
82
|
+
{ active: args_1.codeIndexMode, drainStdout: false, run: (codeIndexModule) => codeIndexModule.runCodeIndexMode() },
|
|
83
|
+
];
|
|
84
|
+
return modes.filter((mode) => mode.active);
|
|
85
|
+
}
|
|
66
86
|
if (args_1.helpMode) {
|
|
67
87
|
printUsage();
|
|
68
88
|
process.exit(0);
|
|
@@ -87,6 +107,10 @@ if (args_1.missingValueOptions.length > 0) {
|
|
|
87
107
|
printUsage();
|
|
88
108
|
process.exit(1);
|
|
89
109
|
}
|
|
110
|
+
if (args_1.command === "update" && args_1.migrateMode) {
|
|
111
|
+
console.error("update cannot be combined with --migrate or --adopt-existing; use project-librarian --migrate for migration.");
|
|
112
|
+
process.exit(1);
|
|
113
|
+
}
|
|
90
114
|
if (args_1.fixMode && !args_1.doctorMode) {
|
|
91
115
|
console.error("--fix is only supported with --doctor.");
|
|
92
116
|
process.exit(1);
|
|
@@ -99,6 +123,10 @@ if (args_1.codeReportSection && !args_1.codeReportMode) {
|
|
|
99
123
|
console.error("--code-report-section is only supported with --code-report.");
|
|
100
124
|
process.exit(1);
|
|
101
125
|
}
|
|
126
|
+
if (args_1.wikiVisualizeOutput && !args_1.wikiVisualizeMode) {
|
|
127
|
+
console.error("--wiki-visualize-out is only supported with --wiki-visualize.");
|
|
128
|
+
process.exit(1);
|
|
129
|
+
}
|
|
102
130
|
if (args_1.codeIndexIncrementalMode && !args_1.codeIndexMode) {
|
|
103
131
|
console.error("--incremental is only supported with --code-index.");
|
|
104
132
|
process.exit(1);
|
|
@@ -134,50 +162,37 @@ else {
|
|
|
134
162
|
runInitCommand();
|
|
135
163
|
}
|
|
136
164
|
function runInitCommand() {
|
|
137
|
-
const activeCodeModes =
|
|
138
|
-
if (activeCodeModes > 1) {
|
|
139
|
-
console.error("Use one code evidence mode at a time: --code-index, --code-query, --code-report, --code-status, --code-files, --code-impact, or --code-search-symbol.");
|
|
165
|
+
const activeCodeModes = activeCodeEvidenceCliModes();
|
|
166
|
+
if (activeCodeModes.length > 1) {
|
|
167
|
+
console.error("Use one code evidence mode at a time: --code-index, --code-index-health, --code-query, --code-report, --code-status, --code-files, --code-impact, --code-context-pack, or --code-search-symbol.");
|
|
140
168
|
process.exit(1);
|
|
141
169
|
}
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
exitAfterStdoutDrain(0);
|
|
170
|
+
const activeCodeMode = activeCodeModes[0];
|
|
171
|
+
if (activeCodeMode) {
|
|
172
|
+
activeCodeMode.run(codeIndex());
|
|
173
|
+
if (activeCodeMode.drainStdout)
|
|
174
|
+
exitAfterStdoutDrain(0);
|
|
175
|
+
else
|
|
176
|
+
process.exit(0);
|
|
150
177
|
return;
|
|
151
178
|
}
|
|
152
|
-
if (args_1.codeStatusMode) {
|
|
153
|
-
codeIndex().runCodeStatusMode();
|
|
154
|
-
exitAfterStdoutDrain(0);
|
|
155
|
-
return;
|
|
156
|
-
}
|
|
157
|
-
if (args_1.codeFilesMode) {
|
|
158
|
-
codeIndex().runCodeFilesMode();
|
|
159
|
-
exitAfterStdoutDrain(0);
|
|
160
|
-
return;
|
|
161
|
-
}
|
|
162
|
-
if (args_1.codeImpactMode) {
|
|
163
|
-
codeIndex().runCodeImpactMode();
|
|
164
|
-
exitAfterStdoutDrain(0);
|
|
165
|
-
return;
|
|
166
|
-
}
|
|
167
|
-
if (args_1.codeSearchSymbolMode) {
|
|
168
|
-
codeIndex().runCodeSearchSymbolMode();
|
|
169
|
-
exitAfterStdoutDrain(0);
|
|
170
|
-
return;
|
|
171
|
-
}
|
|
172
|
-
if (args_1.codeIndexMode) {
|
|
173
|
-
codeIndex().runCodeIndexMode();
|
|
174
|
-
process.exit(0);
|
|
175
|
-
}
|
|
176
179
|
if (args_1.wikiImpactMode) {
|
|
177
180
|
(0, modes_1.runWikiImpactMode)();
|
|
178
181
|
exitAfterStdoutDrain(0);
|
|
179
182
|
return;
|
|
180
183
|
}
|
|
184
|
+
if (args_1.wikiVisualizeMode) {
|
|
185
|
+
try {
|
|
186
|
+
const output = (0, wiki_visualizer_1.writeWikiVisualizer)(args_1.wikiVisualizeOutput);
|
|
187
|
+
console.log(`Project wiki visualizer written: ${output}`);
|
|
188
|
+
exitAfterStdoutDrain(0);
|
|
189
|
+
}
|
|
190
|
+
catch (error) {
|
|
191
|
+
console.error(error instanceof Error ? error.message : String(error));
|
|
192
|
+
process.exit(1);
|
|
193
|
+
}
|
|
194
|
+
return;
|
|
195
|
+
}
|
|
181
196
|
if (args_1.queryTerm) {
|
|
182
197
|
(0, modes_1.runQueryMode)();
|
|
183
198
|
exitAfterStdoutDrain(0);
|
|
@@ -228,6 +243,10 @@ function runInitCommand() {
|
|
|
228
243
|
(0, modes_1.runLintMode)();
|
|
229
244
|
process.exit(0);
|
|
230
245
|
}
|
|
246
|
+
if (args_1.refreshIndexMode && !args_1.migrateMode && !args_1.glossaryMode && !args_1.captureInboxMode) {
|
|
247
|
+
runRefreshIndexOnlyMode();
|
|
248
|
+
process.exit(0);
|
|
249
|
+
}
|
|
231
250
|
const migrationState = args_1.migrateMode ? (0, migration_1.prepareMigrationMode)() : null;
|
|
232
251
|
const results = [];
|
|
233
252
|
if (migrationState)
|
|
@@ -336,3 +355,12 @@ function runInitCommand() {
|
|
|
336
355
|
console.log(`${String(status).padEnd(7)} ${relativePath}`);
|
|
337
356
|
}
|
|
338
357
|
}
|
|
358
|
+
function runRefreshIndexOnlyMode() {
|
|
359
|
+
const results = [];
|
|
360
|
+
results.push(["wiki/index.md", (0, workspace_1.writeStarter)("wiki/index.md", templates_1.index)]);
|
|
361
|
+
results.push(["wiki/index.md auto-discovered pages", (0, workspace_1.upsertMarkedSection)("wiki/index.md", "<!-- PROJECT-WIKI-AUTO-INDEX:START -->", "<!-- PROJECT-WIKI-AUTO-INDEX:END -->", (0, modes_1.buildRefreshIndexBlock)())]);
|
|
362
|
+
console.log("Project Librarian refresh-index complete.");
|
|
363
|
+
for (const [relativePath, status] of results) {
|
|
364
|
+
console.log(`${String(status).padEnd(7)} ${relativePath}`);
|
|
365
|
+
}
|
|
366
|
+
}
|
package/dist/mcp-server.js
CHANGED
|
@@ -11,10 +11,11 @@
|
|
|
11
11
|
// frame on \n and parse each non-empty line as one JSON-RPC message.
|
|
12
12
|
//
|
|
13
13
|
// Methods implemented exactly: initialize, notifications/initialized (no-op),
|
|
14
|
-
// ping,
|
|
15
|
-
// JSON-RPC -
|
|
16
|
-
//
|
|
17
|
-
//
|
|
14
|
+
// ping, resources/list, resources/read, prompts/list, prompts/get, tools/list,
|
|
15
|
+
// tools/call. Unknown method -> JSON-RPC -32601. Parse error -> JSON-RPC -32700
|
|
16
|
+
// with id null. These protocol error responses are MCP/JSON-RPC spec compliance,
|
|
17
|
+
// NOT fallback coding: a malformed frame or unknown method has a single
|
|
18
|
+
// spec-defined reply.
|
|
18
19
|
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
19
20
|
if (k2 === undefined) k2 = k;
|
|
20
21
|
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
@@ -49,7 +50,7 @@ var __importStar = (this && this.__importStar) || (function () {
|
|
|
49
50
|
};
|
|
50
51
|
})();
|
|
51
52
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
52
|
-
exports.TRUST_SENTENCE = exports.TRUNCATION_NOTICE = exports.MAX_RESPONSE_CHARS = exports.SUPPORTED_PROTOCOL_VERSION = void 0;
|
|
53
|
+
exports.PROMPT_TRUNCATION_NOTICE = exports.MAX_PROMPT_CHARS = exports.RESOURCE_TRUNCATION_NOTICE = exports.MAX_RESOURCE_CHARS = exports.TRUST_SENTENCE = exports.TRUNCATION_NOTICE = exports.MAX_RESPONSE_CHARS = exports.SUPPORTED_PROTOCOL_VERSION = void 0;
|
|
53
54
|
exports.scaleGuidanceLines = scaleGuidanceLines;
|
|
54
55
|
exports.runMcpServerMode = runMcpServerMode;
|
|
55
56
|
exports.handleLine = handleLine;
|
|
@@ -57,6 +58,7 @@ const fs = __importStar(require("node:fs"));
|
|
|
57
58
|
const path = __importStar(require("node:path"));
|
|
58
59
|
const code_index_1 = require("./code-index");
|
|
59
60
|
const code_index_file_policy_1 = require("./code-index-file-policy");
|
|
61
|
+
const schema_1 = require("./code-index/schema");
|
|
60
62
|
const workspace_1 = require("./workspace");
|
|
61
63
|
// Pinned MCP protocol version. This is the spec revision this server is written
|
|
62
64
|
// against; one constant so the supported version is auditable in a single place.
|
|
@@ -66,6 +68,7 @@ exports.SUPPORTED_PROTOCOL_VERSION = SUPPORTED_PROTOCOL_VERSION;
|
|
|
66
68
|
const JSONRPC_PARSE_ERROR = -32700;
|
|
67
69
|
const JSONRPC_METHOD_NOT_FOUND = -32601;
|
|
68
70
|
const JSONRPC_INVALID_PARAMS = -32602;
|
|
71
|
+
const JSONRPC_RESOURCE_NOT_FOUND = -32002;
|
|
69
72
|
// Hard cap on a single tool-result text payload. The benchmark finding is that
|
|
70
73
|
// answer-shaped, bounded responses are the hypothesis under test; an unbounded
|
|
71
74
|
// dump would reintroduce the tool-output cost the boundary measured. When a body
|
|
@@ -74,6 +77,14 @@ const MAX_RESPONSE_CHARS = 4000;
|
|
|
74
77
|
exports.MAX_RESPONSE_CHARS = MAX_RESPONSE_CHARS;
|
|
75
78
|
const TRUNCATION_NOTICE = "[truncated — refine the query]";
|
|
76
79
|
exports.TRUNCATION_NOTICE = TRUNCATION_NOTICE;
|
|
80
|
+
const MAX_RESOURCE_CHARS = 6000;
|
|
81
|
+
exports.MAX_RESOURCE_CHARS = MAX_RESOURCE_CHARS;
|
|
82
|
+
const RESOURCE_TRUNCATION_NOTICE = "[truncated - read the backing file directly or narrow the request]";
|
|
83
|
+
exports.RESOURCE_TRUNCATION_NOTICE = RESOURCE_TRUNCATION_NOTICE;
|
|
84
|
+
const MAX_PROMPT_CHARS = 6000;
|
|
85
|
+
exports.MAX_PROMPT_CHARS = MAX_PROMPT_CHARS;
|
|
86
|
+
const PROMPT_TRUNCATION_NOTICE = "[truncated - shorten the prompt arguments]";
|
|
87
|
+
exports.PROMPT_TRUNCATION_NOTICE = PROMPT_TRUNCATION_NOTICE;
|
|
77
88
|
// The trust sentence appended to every tool description (B4 analogue). It tells
|
|
78
89
|
// the agent the index is authoritative for structure questions so it does not
|
|
79
90
|
// re-run repo-wide greps, gated on the staleness signal that code_status reports.
|
|
@@ -96,6 +107,205 @@ function serverInfo() {
|
|
|
96
107
|
}
|
|
97
108
|
}
|
|
98
109
|
// ---------------------------------------------------------------------------
|
|
110
|
+
// Resources and prompts (fixed registry; no arbitrary file reads)
|
|
111
|
+
// ---------------------------------------------------------------------------
|
|
112
|
+
function finalizeResourceText(text) {
|
|
113
|
+
if (text.length <= MAX_RESOURCE_CHARS)
|
|
114
|
+
return text;
|
|
115
|
+
const budget = MAX_RESOURCE_CHARS - RESOURCE_TRUNCATION_NOTICE.length - 1;
|
|
116
|
+
return `${text.slice(0, budget > 0 ? budget : 0).trimEnd()}\n${RESOURCE_TRUNCATION_NOTICE}`;
|
|
117
|
+
}
|
|
118
|
+
function finalizePromptText(text) {
|
|
119
|
+
if (text.length <= MAX_PROMPT_CHARS)
|
|
120
|
+
return text;
|
|
121
|
+
const budget = MAX_PROMPT_CHARS - PROMPT_TRUNCATION_NOTICE.length - 1;
|
|
122
|
+
return `${text.slice(0, budget > 0 ? budget : 0).trimEnd()}\n${PROMPT_TRUNCATION_NOTICE}`;
|
|
123
|
+
}
|
|
124
|
+
function readProjectMarkdown(relativePath) {
|
|
125
|
+
const absolutePath = path.join(workspace_1.root, relativePath);
|
|
126
|
+
if (!fs.existsSync(absolutePath)) {
|
|
127
|
+
return `Resource unavailable: ${relativePath} is missing. Run \`project-librarian\` to bootstrap or refresh the project wiki.`;
|
|
128
|
+
}
|
|
129
|
+
return finalizeResourceText(fs.readFileSync(absolutePath, "utf8"));
|
|
130
|
+
}
|
|
131
|
+
function codeStatusResourceText() {
|
|
132
|
+
let opened;
|
|
133
|
+
try {
|
|
134
|
+
opened = (0, code_index_1.openCodeEvidenceDatabaseForServing)();
|
|
135
|
+
}
|
|
136
|
+
catch (error) {
|
|
137
|
+
if (error instanceof code_index_1.CodeEvidenceIndexUnavailableError) {
|
|
138
|
+
return `Code status unavailable: ${error.message}`;
|
|
139
|
+
}
|
|
140
|
+
throw error;
|
|
141
|
+
}
|
|
142
|
+
try {
|
|
143
|
+
const staleness = (0, code_index_1.codeIndexStaleness)(opened.database);
|
|
144
|
+
return finalizeResourceText(statusAnswer(opened.database, opened.relativePath, staleness));
|
|
145
|
+
}
|
|
146
|
+
finally {
|
|
147
|
+
opened.database.close();
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
const RESOURCES = [
|
|
151
|
+
{
|
|
152
|
+
uri: "project-librarian://wiki/startup",
|
|
153
|
+
name: "wiki-startup",
|
|
154
|
+
title: "Project Wiki Startup",
|
|
155
|
+
description: "Compact session-start project context from wiki/startup.md.",
|
|
156
|
+
mimeType: "text/markdown",
|
|
157
|
+
read: () => readProjectMarkdown("wiki/startup.md"),
|
|
158
|
+
},
|
|
159
|
+
{
|
|
160
|
+
uri: "project-librarian://wiki/index",
|
|
161
|
+
name: "wiki-index",
|
|
162
|
+
title: "Project Wiki Index",
|
|
163
|
+
description: "Project wiki router from wiki/index.md.",
|
|
164
|
+
mimeType: "text/markdown",
|
|
165
|
+
read: () => readProjectMarkdown("wiki/index.md"),
|
|
166
|
+
},
|
|
167
|
+
{
|
|
168
|
+
uri: "project-librarian://code/status",
|
|
169
|
+
name: "code-status",
|
|
170
|
+
title: "Code Evidence Status",
|
|
171
|
+
description: "Current code-evidence freshness, coverage, and scale guidance.",
|
|
172
|
+
mimeType: "text/plain",
|
|
173
|
+
read: codeStatusResourceText,
|
|
174
|
+
},
|
|
175
|
+
];
|
|
176
|
+
const RESOURCES_BY_URI = new Map(RESOURCES.map((resource) => [resource.uri, resource]));
|
|
177
|
+
function resourceDescriptor(resource) {
|
|
178
|
+
return {
|
|
179
|
+
uri: resource.uri,
|
|
180
|
+
name: resource.name,
|
|
181
|
+
title: resource.title,
|
|
182
|
+
description: resource.description,
|
|
183
|
+
mimeType: resource.mimeType,
|
|
184
|
+
annotations: { audience: ["assistant"], priority: resource.uri === "project-librarian://code/status" ? 0.7 : 0.9 },
|
|
185
|
+
};
|
|
186
|
+
}
|
|
187
|
+
function readResource(uri) {
|
|
188
|
+
if (typeof uri !== "string" || uri.trim() === "") {
|
|
189
|
+
throw new ToolArgumentError("missing required string argument: uri");
|
|
190
|
+
}
|
|
191
|
+
const resource = RESOURCES_BY_URI.get(uri.trim());
|
|
192
|
+
if (!resource) {
|
|
193
|
+
throw new ResourceNotFoundError(`Resource not found: ${uri}`);
|
|
194
|
+
}
|
|
195
|
+
return {
|
|
196
|
+
contents: [{
|
|
197
|
+
uri: resource.uri,
|
|
198
|
+
mimeType: resource.mimeType,
|
|
199
|
+
text: resource.read(),
|
|
200
|
+
}],
|
|
201
|
+
};
|
|
202
|
+
}
|
|
203
|
+
function optionalPromptArg(args, key, placeholder) {
|
|
204
|
+
const value = args[key];
|
|
205
|
+
return typeof value === "string" && value.trim() !== "" ? value.trim() : placeholder;
|
|
206
|
+
}
|
|
207
|
+
function requiredPromptArg(args, key) {
|
|
208
|
+
const value = args[key];
|
|
209
|
+
if (typeof value !== "string" || value.trim() === "") {
|
|
210
|
+
throw new ToolArgumentError(`missing required string argument: ${key}`);
|
|
211
|
+
}
|
|
212
|
+
return value.trim();
|
|
213
|
+
}
|
|
214
|
+
const PROMPTS = [
|
|
215
|
+
{
|
|
216
|
+
name: "wiki_taxonomy_update",
|
|
217
|
+
title: "Wiki Taxonomy Update",
|
|
218
|
+
description: "Classify and apply project-planning content through the wiki taxonomy before editing.",
|
|
219
|
+
arguments: [
|
|
220
|
+
{ name: "content", description: "Project-planning content to classify and apply.", required: true },
|
|
221
|
+
{ name: "target", description: "Optional intended wiki area or page." },
|
|
222
|
+
],
|
|
223
|
+
get: (args) => {
|
|
224
|
+
const content = requiredPromptArg(args, "content");
|
|
225
|
+
const target = optionalPromptArg(args, "target", "choose the canonical, decision, source, or meta target from wiki/meta/document-taxonomy.md");
|
|
226
|
+
return [
|
|
227
|
+
"Use the Project Librarian wiki contract for this planning update.",
|
|
228
|
+
"1. Read project-librarian://wiki/startup and project-librarian://wiki/index first.",
|
|
229
|
+
"2. Classify the content with wiki/meta/document-taxonomy.md before editing.",
|
|
230
|
+
"3. Update the wiki in the same turn, then refresh the index and run wiki lint.",
|
|
231
|
+
`Target hint: ${target}`,
|
|
232
|
+
"Planning content:",
|
|
233
|
+
content,
|
|
234
|
+
].join("\n");
|
|
235
|
+
},
|
|
236
|
+
},
|
|
237
|
+
{
|
|
238
|
+
name: "code_impact_trace",
|
|
239
|
+
title: "Code Impact Trace",
|
|
240
|
+
description: "Trace a path, symbol, route, or module through code evidence before planning a change.",
|
|
241
|
+
arguments: [
|
|
242
|
+
{ name: "term", description: "Path, symbol, route, module, or concept to trace.", required: true },
|
|
243
|
+
{ name: "question", description: "Optional user question or change intent." },
|
|
244
|
+
],
|
|
245
|
+
get: (args) => {
|
|
246
|
+
const term = requiredPromptArg(args, "term");
|
|
247
|
+
const question = optionalPromptArg(args, "question", "identify impacted files, owners, imports, routes, and tests before editing");
|
|
248
|
+
return [
|
|
249
|
+
`Build a code impact trace for "${term}".`,
|
|
250
|
+
"1. Read project-librarian://code/status and stop to rebuild the index if it is stale.",
|
|
251
|
+
"2. Call code_context_pack first for bounded first-pass context.",
|
|
252
|
+
"3. Call code_impact only when deeper incoming/outgoing edge detail is needed.",
|
|
253
|
+
"4. Report paths, symbols, route handlers, import aliases, owners, and missing test coverage before editing.",
|
|
254
|
+
`Question: ${question}`,
|
|
255
|
+
].join("\n");
|
|
256
|
+
},
|
|
257
|
+
},
|
|
258
|
+
{
|
|
259
|
+
name: "retrieval_quality_review",
|
|
260
|
+
title: "Retrieval Quality Review",
|
|
261
|
+
description: "Review whether wiki/code retrieval evidence is precise, complete, bounded, and stale-aware.",
|
|
262
|
+
arguments: [
|
|
263
|
+
{ name: "query", description: "The retrieval query or task being evaluated.", required: true },
|
|
264
|
+
{ name: "expected_evidence", description: "Optional expected files, blocks, symbols, or answer terms." },
|
|
265
|
+
],
|
|
266
|
+
get: (args) => {
|
|
267
|
+
const query = requiredPromptArg(args, "query");
|
|
268
|
+
const expected = optionalPromptArg(args, "expected_evidence", "state the expected files, wiki blocks, symbols, answer terms, or say unknown");
|
|
269
|
+
return [
|
|
270
|
+
`Review retrieval quality for query "${query}".`,
|
|
271
|
+
"Use project-librarian://wiki/startup, project-librarian://wiki/index, and project-librarian://code/status as the first context sources.",
|
|
272
|
+
"Evaluate source hit rate, evidence precision, block integrity, graph hop usefulness, scan/output byte discipline, stale-state handling, and answer correctness.",
|
|
273
|
+
"Flag broad pages, hub expansion noise, source-snippet leakage, missing edge cases, and any unsupported quality claim.",
|
|
274
|
+
`Expected evidence: ${expected}`,
|
|
275
|
+
].join("\n");
|
|
276
|
+
},
|
|
277
|
+
},
|
|
278
|
+
];
|
|
279
|
+
const PROMPTS_BY_NAME = new Map(PROMPTS.map((prompt) => [prompt.name, prompt]));
|
|
280
|
+
function promptDescriptor(prompt) {
|
|
281
|
+
return {
|
|
282
|
+
name: prompt.name,
|
|
283
|
+
title: prompt.title,
|
|
284
|
+
description: prompt.description,
|
|
285
|
+
arguments: prompt.arguments,
|
|
286
|
+
};
|
|
287
|
+
}
|
|
288
|
+
function getPrompt(name, rawArgs) {
|
|
289
|
+
if (typeof name !== "string" || name.trim() === "") {
|
|
290
|
+
throw new ToolArgumentError("missing required string argument: name");
|
|
291
|
+
}
|
|
292
|
+
const prompt = PROMPTS_BY_NAME.get(name.trim());
|
|
293
|
+
if (!prompt) {
|
|
294
|
+
throw new ResourceNotFoundError(`Prompt not found: ${name}`);
|
|
295
|
+
}
|
|
296
|
+
const args = rawArgs && typeof rawArgs === "object" && !Array.isArray(rawArgs) ? rawArgs : {};
|
|
297
|
+
return {
|
|
298
|
+
description: prompt.description,
|
|
299
|
+
messages: [{
|
|
300
|
+
role: "user",
|
|
301
|
+
content: {
|
|
302
|
+
type: "text",
|
|
303
|
+
text: finalizePromptText(prompt.get(args)),
|
|
304
|
+
},
|
|
305
|
+
}],
|
|
306
|
+
};
|
|
307
|
+
}
|
|
308
|
+
// ---------------------------------------------------------------------------
|
|
99
309
|
// Answer-shape helpers
|
|
100
310
|
// ---------------------------------------------------------------------------
|
|
101
311
|
function requireStringArg(args, key) {
|
|
@@ -108,6 +318,9 @@ function requireStringArg(args, key) {
|
|
|
108
318
|
// Thrown for bad tool arguments; surfaces as a JSON-RPC -32602 invalid params.
|
|
109
319
|
class ToolArgumentError extends Error {
|
|
110
320
|
}
|
|
321
|
+
// Thrown for unknown fixed resources/prompts; surfaces as JSON-RPC -32002.
|
|
322
|
+
class ResourceNotFoundError extends Error {
|
|
323
|
+
}
|
|
111
324
|
// Prepend a single staleness warning line when the index is stale, then enforce
|
|
112
325
|
// the hard char cap with an explicit truncation notice. The warning is counted
|
|
113
326
|
// against the cap so the cap is a true ceiling on the returned text.
|
|
@@ -154,8 +367,8 @@ function ownershipAnswer(filePath) {
|
|
|
154
367
|
lines.push(`Workspace owner: ${info.owner} (source: ${info.owner_source})${info.codeowners ? `; codeowners ${info.codeowners}` : ""}.`);
|
|
155
368
|
return lines.join("\n");
|
|
156
369
|
}
|
|
157
|
-
function impactAnswer(database, term) {
|
|
158
|
-
const impact = (0, code_index_1.codeImpact)(database, term);
|
|
370
|
+
function impactAnswer(database, term, context) {
|
|
371
|
+
const impact = (0, code_index_1.codeImpact)(database, term, { staleness: context.staleness });
|
|
159
372
|
const matches = (impact.matches ?? {});
|
|
160
373
|
const edges = (impact.edges ?? {});
|
|
161
374
|
const owners = asRows(impact.impacted_owners);
|
|
@@ -263,6 +476,16 @@ function statusAnswer(database, relativePath, staleness) {
|
|
|
263
476
|
return lines.join("\n");
|
|
264
477
|
}
|
|
265
478
|
const TOOLS = [
|
|
479
|
+
{
|
|
480
|
+
name: "code_context_pack",
|
|
481
|
+
description: `Budgeted first-pass context for a path, symbol, route, or module term: matching files, symbols, routes, imports, edges, owners, freshness, and scale guidance without source snippets. ${TRUST_SENTENCE}`,
|
|
482
|
+
inputSchema: {
|
|
483
|
+
type: "object",
|
|
484
|
+
properties: { term: { type: "string", description: "Path, symbol, route, module, or concept to build a compact code context pack for." } },
|
|
485
|
+
required: ["term"],
|
|
486
|
+
},
|
|
487
|
+
run: (database, args, context) => (0, code_index_1.codeContextPack)(database, requireStringArg(args, "term"), { staleness: context.staleness }),
|
|
488
|
+
},
|
|
266
489
|
{
|
|
267
490
|
name: "code_impact",
|
|
268
491
|
description: `Impact of a file, symbol, route, or module term across the indexed code: matching symbols/signatures, routes, imports, dependent edges, and impacted owners. ${TRUST_SENTENCE}`,
|
|
@@ -271,7 +494,7 @@ const TOOLS = [
|
|
|
271
494
|
properties: { term: { type: "string", description: "File path, symbol name, route, or module to trace." } },
|
|
272
495
|
required: ["term"],
|
|
273
496
|
},
|
|
274
|
-
run: (database, args) => impactAnswer(database, requireStringArg(args, "term")),
|
|
497
|
+
run: (database, args, context) => impactAnswer(database, requireStringArg(args, "term"), context),
|
|
275
498
|
},
|
|
276
499
|
{
|
|
277
500
|
name: "code_ownership",
|
|
@@ -315,6 +538,59 @@ const TOOLS = [
|
|
|
315
538
|
},
|
|
316
539
|
];
|
|
317
540
|
const TOOLS_BY_NAME = new Map(TOOLS.map((tool) => [tool.name, tool]));
|
|
541
|
+
let cachedStaleness = null;
|
|
542
|
+
function statFingerprint(relativePath) {
|
|
543
|
+
const absolutePath = path.join(workspace_1.root, relativePath);
|
|
544
|
+
try {
|
|
545
|
+
const stat = fs.statSync(absolutePath);
|
|
546
|
+
if (!stat.isFile())
|
|
547
|
+
return `${relativePath}:not-file`;
|
|
548
|
+
return [
|
|
549
|
+
relativePath,
|
|
550
|
+
stat.size,
|
|
551
|
+
stat.mtimeMs,
|
|
552
|
+
stat.ctimeMs,
|
|
553
|
+
].join(":");
|
|
554
|
+
}
|
|
555
|
+
catch {
|
|
556
|
+
return `${relativePath}:missing`;
|
|
557
|
+
}
|
|
558
|
+
}
|
|
559
|
+
function codeEvidenceFreshnessCacheKey(opened) {
|
|
560
|
+
try {
|
|
561
|
+
const database = opened.database;
|
|
562
|
+
const scopes = (0, schema_1.indexedScopes)(database);
|
|
563
|
+
const parserMode = (0, schema_1.indexedParserMode)(database);
|
|
564
|
+
const schemaVersion = (0, schema_1.readMetaValue)(database, "schema_version");
|
|
565
|
+
const updatedAt = (0, schema_1.readMetaValue)(database, "updated_at");
|
|
566
|
+
const currentFiles = (0, code_index_file_policy_1.discoverCodeFiles)(scopes.length > 0 ? scopes : ["."]);
|
|
567
|
+
const currentFingerprint = currentFiles.map(statFingerprint).join("\n");
|
|
568
|
+
const indexedFingerprint = database.prepare("SELECT path, hash, mtime_ms, size FROM files ORDER BY path")
|
|
569
|
+
.all()
|
|
570
|
+
.map((row) => `${String(row.path)}:${String(row.hash)}:${Number(row.mtime_ms)}:${Number(row.size)}`)
|
|
571
|
+
.join("\n");
|
|
572
|
+
return [
|
|
573
|
+
opened.relativePath,
|
|
574
|
+
schemaVersion,
|
|
575
|
+
updatedAt,
|
|
576
|
+
parserMode,
|
|
577
|
+
scopes.join("\0"),
|
|
578
|
+
currentFingerprint,
|
|
579
|
+
indexedFingerprint,
|
|
580
|
+
].join("\n---\n");
|
|
581
|
+
}
|
|
582
|
+
catch {
|
|
583
|
+
return null;
|
|
584
|
+
}
|
|
585
|
+
}
|
|
586
|
+
function codeIndexStalenessForToolCall(opened) {
|
|
587
|
+
const cacheKey = codeEvidenceFreshnessCacheKey(opened);
|
|
588
|
+
if (cacheKey && cachedStaleness?.cacheKey === cacheKey)
|
|
589
|
+
return cachedStaleness.staleness;
|
|
590
|
+
const staleness = (0, code_index_1.codeIndexStaleness)(opened.database);
|
|
591
|
+
cachedStaleness = cacheKey ? { cacheKey, staleness } : null;
|
|
592
|
+
return staleness;
|
|
593
|
+
}
|
|
318
594
|
// ---------------------------------------------------------------------------
|
|
319
595
|
// tools/call dispatch
|
|
320
596
|
// ---------------------------------------------------------------------------
|
|
@@ -337,10 +613,10 @@ function callTool(name, rawArgs) {
|
|
|
337
613
|
throw error;
|
|
338
614
|
}
|
|
339
615
|
try {
|
|
340
|
-
const staleness = (
|
|
616
|
+
const staleness = codeIndexStalenessForToolCall(opened);
|
|
341
617
|
const body = name === "code_status"
|
|
342
618
|
? statusAnswer(opened.database, opened.relativePath, staleness)
|
|
343
|
-
: TOOLS_BY_NAME.get(name).run(opened.database, args);
|
|
619
|
+
: TOOLS_BY_NAME.get(name).run(opened.database, args, { staleness });
|
|
344
620
|
return toolResultContent(finalizeAnswer(body, staleness));
|
|
345
621
|
}
|
|
346
622
|
catch (error) {
|
|
@@ -380,11 +656,49 @@ function handleRequest(request) {
|
|
|
380
656
|
case "initialize":
|
|
381
657
|
return successResponse(id, {
|
|
382
658
|
protocolVersion: negotiatedProtocolVersion(request.params),
|
|
383
|
-
capabilities: { tools: {} },
|
|
659
|
+
capabilities: { tools: {}, resources: {}, prompts: {} },
|
|
384
660
|
serverInfo: serverInfo(),
|
|
385
661
|
});
|
|
386
662
|
case "ping":
|
|
387
663
|
return successResponse(id, {});
|
|
664
|
+
case "resources/list":
|
|
665
|
+
return successResponse(id, {
|
|
666
|
+
resources: RESOURCES.map(resourceDescriptor),
|
|
667
|
+
});
|
|
668
|
+
case "resources/read": {
|
|
669
|
+
const params = request.params && typeof request.params === "object" ? request.params : {};
|
|
670
|
+
try {
|
|
671
|
+
return successResponse(id, readResource(params.uri));
|
|
672
|
+
}
|
|
673
|
+
catch (error) {
|
|
674
|
+
if (error instanceof ToolArgumentError) {
|
|
675
|
+
return errorResponse(id, { code: JSONRPC_INVALID_PARAMS, message: error.message });
|
|
676
|
+
}
|
|
677
|
+
if (error instanceof ResourceNotFoundError) {
|
|
678
|
+
return errorResponse(id, { code: JSONRPC_RESOURCE_NOT_FOUND, message: error.message });
|
|
679
|
+
}
|
|
680
|
+
throw error;
|
|
681
|
+
}
|
|
682
|
+
}
|
|
683
|
+
case "prompts/list":
|
|
684
|
+
return successResponse(id, {
|
|
685
|
+
prompts: PROMPTS.map(promptDescriptor),
|
|
686
|
+
});
|
|
687
|
+
case "prompts/get": {
|
|
688
|
+
const params = request.params && typeof request.params === "object" ? request.params : {};
|
|
689
|
+
try {
|
|
690
|
+
return successResponse(id, getPrompt(params.name, params.arguments));
|
|
691
|
+
}
|
|
692
|
+
catch (error) {
|
|
693
|
+
if (error instanceof ToolArgumentError) {
|
|
694
|
+
return errorResponse(id, { code: JSONRPC_INVALID_PARAMS, message: error.message });
|
|
695
|
+
}
|
|
696
|
+
if (error instanceof ResourceNotFoundError) {
|
|
697
|
+
return errorResponse(id, { code: JSONRPC_RESOURCE_NOT_FOUND, message: error.message });
|
|
698
|
+
}
|
|
699
|
+
throw error;
|
|
700
|
+
}
|
|
701
|
+
}
|
|
388
702
|
case "tools/list":
|
|
389
703
|
return successResponse(id, {
|
|
390
704
|
tools: TOOLS.map((tool) => ({ name: tool.name, description: tool.description, inputSchema: tool.inputSchema })),
|
package/dist/migration.js
CHANGED
|
@@ -37,6 +37,7 @@ exports.generatedMigrationInboxFiles = void 0;
|
|
|
37
37
|
exports.classifyMarkdown = classifyMarkdown;
|
|
38
38
|
exports.formOnlyMigrationDocumentReason = formOnlyMigrationDocumentReason;
|
|
39
39
|
exports.extractMigrationUnits = extractMigrationUnits;
|
|
40
|
+
exports.loadMigrationUnitContext = loadMigrationUnitContext;
|
|
40
41
|
exports.collectMigrationCoverageDiagnostics = collectMigrationCoverageDiagnostics;
|
|
41
42
|
exports.collectMigrationUnitMapDiagnostics = collectMigrationUnitMapDiagnostics;
|
|
42
43
|
exports.collectMigrationSplitPlanDiagnostics = collectMigrationSplitPlanDiagnostics;
|
|
@@ -321,6 +322,9 @@ function expectedMigrationUnits() {
|
|
|
321
322
|
.flatMap((legacyRoot) => (0, wiki_files_1.walkMarkdownFiles)((0, workspace_1.abs)(legacyRoot), [], (0, workspace_1.abs)(legacyRoot)))
|
|
322
323
|
.flatMap((file) => extractMigrationUnits(file.basePath, (0, workspace_1.read)(file.path)));
|
|
323
324
|
}
|
|
325
|
+
function loadMigrationUnitContext() {
|
|
326
|
+
return { units: expectedMigrationUnits() };
|
|
327
|
+
}
|
|
324
328
|
function isMigrationConfidence(value) {
|
|
325
329
|
return ["high", "medium", "low"].includes(value);
|
|
326
330
|
}
|
|
@@ -353,8 +357,8 @@ function isReviewedCoverageRetarget(cells) {
|
|
|
353
357
|
return note.includes("reviewed low-confidence content; retargeted")
|
|
354
358
|
|| reason.includes("reviewed source context; taxonomy target assigned");
|
|
355
359
|
}
|
|
356
|
-
function collectMigrationCoverageDiagnostics() {
|
|
357
|
-
const units =
|
|
360
|
+
function collectMigrationCoverageDiagnostics(context = loadMigrationUnitContext()) {
|
|
361
|
+
const units = context.units;
|
|
358
362
|
if (units.length === 0)
|
|
359
363
|
return [];
|
|
360
364
|
if (!(0, workspace_1.exists)("wiki/migration/coverage.md")) {
|
|
@@ -417,8 +421,8 @@ function collectMigrationCoverageDiagnostics() {
|
|
|
417
421
|
}
|
|
418
422
|
return diagnostics.sort((a, b) => a.file.localeCompare(b.file) || a.code.localeCompare(b.code) || a.message.localeCompare(b.message));
|
|
419
423
|
}
|
|
420
|
-
function collectMigrationUnitMapDiagnostics() {
|
|
421
|
-
const units =
|
|
424
|
+
function collectMigrationUnitMapDiagnostics(context = loadMigrationUnitContext()) {
|
|
425
|
+
const units = context.units;
|
|
422
426
|
if (units.length === 0)
|
|
423
427
|
return [];
|
|
424
428
|
const file = "wiki/migration/unit-map.md";
|
|
@@ -485,8 +489,8 @@ function collectMigrationUnitMapDiagnostics() {
|
|
|
485
489
|
}
|
|
486
490
|
return diagnostics.sort((a, b) => a.file.localeCompare(b.file) || a.code.localeCompare(b.code) || a.message.localeCompare(b.message));
|
|
487
491
|
}
|
|
488
|
-
function collectMigrationSplitPlanDiagnostics() {
|
|
489
|
-
const units =
|
|
492
|
+
function collectMigrationSplitPlanDiagnostics(context = loadMigrationUnitContext()) {
|
|
493
|
+
const units = context.units;
|
|
490
494
|
if (units.length === 0)
|
|
491
495
|
return [];
|
|
492
496
|
const file = "wiki/migration/split-plan.md";
|