@ppdocs/mcp 3.7.1 → 3.8.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/dist/cli.js +25 -19
- package/dist/config.js +19 -2
- package/dist/storage/httpClient.js +18 -8
- package/dist/tools/doc_query.js +21 -30
- package/dist/tools/flowchart.js +24 -31
- package/dist/tools/init.js +16 -2
- package/package.json +1 -1
- package/templates/AGENT.md +1 -1
- package/templates/commands/pp/init.md +1 -1
- package/templates/commands/pp/sync.md +1 -1
- package/templates/cursorrules.md +1 -1
- package/templates/kiro-rules/ppdocs.md +1 -1
package/dist/cli.js
CHANGED
|
@@ -137,7 +137,7 @@ export async function runCli(args) {
|
|
|
137
137
|
/** 轻量绑定: 仅创建 .ppdocs, 不注册 MCP 不安装模板 */
|
|
138
138
|
function bindProject(opts) {
|
|
139
139
|
writePpdocsConfig(process.cwd(), opts);
|
|
140
|
-
console.log(`\n🔗 Project bound: ${opts.project}\n AI will auto-connect
|
|
140
|
+
console.log(`\n🔗 Project bound: ${opts.project}\n AI will auto-connect when opening this directory.\n`);
|
|
141
141
|
}
|
|
142
142
|
/** 写入 .ppdocs 配置文件 (init + bind 复用) */
|
|
143
143
|
function writePpdocsConfig(cwd, opts) {
|
|
@@ -199,7 +199,7 @@ async function initProject(opts) {
|
|
|
199
199
|
}
|
|
200
200
|
}
|
|
201
201
|
// 自动检测并注册 MCP (如果已写入 Antigravity 配置,跳过 gemini CLI 注册避免冲突)
|
|
202
|
-
const registered = autoRegisterMcp(detectedIdes.includes('antigravity'));
|
|
202
|
+
const registered = autoRegisterMcp(cwd, apiUrl, opts.user || generateUser(), detectedIdes.includes('antigravity'));
|
|
203
203
|
// 如果没有检测到任何 AI CLI,并且也没有检测到配置文件夹,创建 .mcp.json 作为备用
|
|
204
204
|
if (!registered && !hasIdeDir) {
|
|
205
205
|
createMcpJson(cwd, apiUrl, opts.user);
|
|
@@ -254,27 +254,29 @@ function execSilent(cmd) {
|
|
|
254
254
|
}
|
|
255
255
|
catch { /* ignore */ }
|
|
256
256
|
}
|
|
257
|
-
/** 自动检测 AI CLI 并注册全局 MCP
|
|
258
|
-
function autoRegisterMcp(skipGemini = false) {
|
|
257
|
+
/** 自动检测 AI CLI 并注册全局 MCP,直接注入项目环境变量实现开箱即用 */
|
|
258
|
+
function autoRegisterMcp(cwd, apiUrl, user, skipGemini = false) {
|
|
259
259
|
const detected = [];
|
|
260
260
|
const serverName = 'ppdocs-kg';
|
|
261
261
|
const addCmd = `-- npx -y @ppdocs/mcp@latest`;
|
|
262
|
-
|
|
262
|
+
const envPairs = [
|
|
263
|
+
`PPDOCS_API_URL=${apiUrl}`,
|
|
264
|
+
`PPDOCS_USER=${user}`,
|
|
265
|
+
`PPDOCS_PROJECT_ROOT=${cwd}`,
|
|
266
|
+
];
|
|
267
|
+
const claudeEnvArgs = envPairs.map((pair) => `-e ${JSON.stringify(pair)}`).join(' ');
|
|
268
|
+
const codexEnvArgs = envPairs.map((pair) => `--env ${JSON.stringify(pair)}`).join(' ');
|
|
269
|
+
// Claude CLI
|
|
263
270
|
if (commandExists('claude')) {
|
|
264
271
|
detected.push('Claude');
|
|
265
272
|
try {
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
console.log(`✅ Claude MCP already configured`);
|
|
269
|
-
}
|
|
270
|
-
else {
|
|
271
|
-
execSync(`claude mcp add ${serverName} ${addCmd}`, { stdio: 'inherit' });
|
|
272
|
-
}
|
|
273
|
+
execSilent(`claude mcp remove ${serverName}`);
|
|
274
|
+
execSync(`claude mcp add ${claudeEnvArgs} ${serverName} ${addCmd}`, { stdio: 'inherit' });
|
|
273
275
|
}
|
|
274
276
|
catch {
|
|
275
277
|
try {
|
|
276
278
|
execSilent(`claude mcp remove ${serverName}`);
|
|
277
|
-
execSync(`claude mcp add ${serverName} ${addCmd}`, { stdio: 'inherit' });
|
|
279
|
+
execSync(`claude mcp add ${claudeEnvArgs} ${serverName} ${addCmd}`, { stdio: 'inherit' });
|
|
278
280
|
}
|
|
279
281
|
catch {
|
|
280
282
|
console.log(`⚠️ Claude MCP registration failed`);
|
|
@@ -286,7 +288,7 @@ function autoRegisterMcp(skipGemini = false) {
|
|
|
286
288
|
detected.push('Codex');
|
|
287
289
|
try {
|
|
288
290
|
execSilent(`codex mcp remove ${serverName}`);
|
|
289
|
-
execSync(`codex mcp add ${serverName} ${addCmd}`, { stdio: 'inherit' });
|
|
291
|
+
execSync(`codex mcp add ${codexEnvArgs} ${serverName} ${addCmd}`, { stdio: 'inherit' });
|
|
290
292
|
}
|
|
291
293
|
catch {
|
|
292
294
|
console.log(`⚠️ Codex MCP registration failed`);
|
|
@@ -310,7 +312,7 @@ function autoRegisterMcp(skipGemini = false) {
|
|
|
310
312
|
console.log(`✅ Registered MCP for: ${detected.join(', ')}`);
|
|
311
313
|
return true;
|
|
312
314
|
}
|
|
313
|
-
/** 在指定路径创建或更新 mcp.json 配置,注入
|
|
315
|
+
/** 在指定路径创建或更新 mcp.json 配置,注入 PPDOCS_* 环境变量实现启动即就绪 */
|
|
314
316
|
function createMcpConfigAt(mcpPath, apiUrl, options) {
|
|
315
317
|
let mcpConfig = {};
|
|
316
318
|
if (fs.existsSync(mcpPath)) {
|
|
@@ -333,6 +335,8 @@ function createMcpConfigAt(mcpPath, apiUrl, options) {
|
|
|
333
335
|
env.PPDOCS_API_URL = apiUrl;
|
|
334
336
|
if (options?.user)
|
|
335
337
|
env.PPDOCS_USER = options.user;
|
|
338
|
+
if (options?.projectRoot)
|
|
339
|
+
env.PPDOCS_PROJECT_ROOT = options.projectRoot;
|
|
336
340
|
}
|
|
337
341
|
const ppdocsServer = isWindows
|
|
338
342
|
? { command: 'cmd', args: ['/c', 'npx', '-y', '@ppdocs/mcp@latest'], ...(Object.keys(env).length > 0 && { env }) }
|
|
@@ -351,7 +355,7 @@ function createMcpConfigAt(mcpPath, apiUrl, options) {
|
|
|
351
355
|
}
|
|
352
356
|
/** 创建 .mcp.json (手动配置备用) */
|
|
353
357
|
function createMcpJson(cwd, apiUrl, user) {
|
|
354
|
-
createMcpConfigAt(path.join(cwd, '.mcp.json'), apiUrl, { user });
|
|
358
|
+
createMcpConfigAt(path.join(cwd, '.mcp.json'), apiUrl, { user, projectRoot: cwd });
|
|
355
359
|
}
|
|
356
360
|
/** 递归复制目录 */
|
|
357
361
|
function copyDirRecursive(src, dest) {
|
|
@@ -400,6 +404,8 @@ function generateMcpPermissions() {
|
|
|
400
404
|
'kg_ref',
|
|
401
405
|
// 流程图
|
|
402
406
|
'kg_flowchart',
|
|
407
|
+
// 文档查询
|
|
408
|
+
'kg_doc',
|
|
403
409
|
// 协作
|
|
404
410
|
'kg_meeting',
|
|
405
411
|
// 代码分析
|
|
@@ -485,7 +491,7 @@ function installGlobalRules(cwd, filename) {
|
|
|
485
491
|
}
|
|
486
492
|
/** 安装 Cursor 模板 */
|
|
487
493
|
function installCursorTemplates(cwd, apiUrl, user) {
|
|
488
|
-
createMcpConfigAt(path.join(cwd, '.cursor', 'mcp.json'), apiUrl, { user });
|
|
494
|
+
createMcpConfigAt(path.join(cwd, '.cursor', 'mcp.json'), apiUrl, { user, projectRoot: cwd });
|
|
489
495
|
// 2. 生成 .cursor/rules/ppdocs.md (官方标准, .cursorrules 已废弃)
|
|
490
496
|
const cursorRulesDir = path.join(cwd, '.cursor', 'rules');
|
|
491
497
|
if (!fs.existsSync(cursorRulesDir)) {
|
|
@@ -578,7 +584,7 @@ function installWorkflows(cwd, extraDirs = []) {
|
|
|
578
584
|
/** 安装 Antigravity (Gemini IDE) 模板 */
|
|
579
585
|
function installAntigravityTemplates(cwd, apiUrl, user) {
|
|
580
586
|
// Antigravity 由 gemini CLI 注册 env,文件配置不注入避免冲突
|
|
581
|
-
createMcpConfigAt(path.join(cwd, '.gemini', 'settings.json'), apiUrl, { noEnv: true, user });
|
|
587
|
+
createMcpConfigAt(path.join(cwd, '.gemini', 'settings.json'), apiUrl, { noEnv: true, user, projectRoot: cwd });
|
|
582
588
|
// 2. 生成 GEMINI.md 全局规则 (Gemini CLI 官方标准)
|
|
583
589
|
installGlobalRules(cwd, 'GEMINI.md');
|
|
584
590
|
// 3. Antigravity 全局 workflows 目录 (额外安装目标)
|
|
@@ -592,7 +598,7 @@ function installAntigravityTemplates(cwd, apiUrl, user) {
|
|
|
592
598
|
}
|
|
593
599
|
/** 安装 Kiro 模板 */
|
|
594
600
|
function installKiroTemplates(cwd, apiUrl, user) {
|
|
595
|
-
createMcpConfigAt(path.join(cwd, '.kiro', 'settings', 'mcp.json'), apiUrl, { user });
|
|
601
|
+
createMcpConfigAt(path.join(cwd, '.kiro', 'settings', 'mcp.json'), apiUrl, { user, projectRoot: cwd });
|
|
596
602
|
// 2. 生成 Kiro Steering 文件 (官方标准: .kiro/steering/ 而非 rules/)
|
|
597
603
|
const steeringDir = path.join(cwd, '.kiro', 'steering');
|
|
598
604
|
if (!fs.existsSync(steeringDir)) {
|
package/dist/config.js
CHANGED
|
@@ -24,9 +24,26 @@ function readEnvConfig() {
|
|
|
24
24
|
source: 'env',
|
|
25
25
|
};
|
|
26
26
|
}
|
|
27
|
+
function findPpdocsFile(startDir = process.cwd()) {
|
|
28
|
+
let currentDir = path.resolve(startDir);
|
|
29
|
+
while (true) {
|
|
30
|
+
const configPath = path.join(currentDir, PPDOCS_CONFIG_FILE);
|
|
31
|
+
if (fs.existsSync(configPath) && fs.statSync(configPath).isFile()) {
|
|
32
|
+
return configPath;
|
|
33
|
+
}
|
|
34
|
+
const parentDir = path.dirname(currentDir);
|
|
35
|
+
if (parentDir === currentDir) {
|
|
36
|
+
return null;
|
|
37
|
+
}
|
|
38
|
+
currentDir = parentDir;
|
|
39
|
+
}
|
|
40
|
+
}
|
|
27
41
|
function readPpdocsFile() {
|
|
28
|
-
const
|
|
29
|
-
|
|
42
|
+
const hintedRoot = process.env.PPDOCS_PROJECT_ROOT;
|
|
43
|
+
const configPath = hintedRoot
|
|
44
|
+
? path.join(path.resolve(hintedRoot), PPDOCS_CONFIG_FILE)
|
|
45
|
+
: findPpdocsFile();
|
|
46
|
+
if (!configPath || !fs.existsSync(configPath))
|
|
30
47
|
return null;
|
|
31
48
|
try {
|
|
32
49
|
const c = JSON.parse(fs.readFileSync(configPath, 'utf-8'));
|
|
@@ -9,6 +9,17 @@
|
|
|
9
9
|
function cleanPath(p) {
|
|
10
10
|
return p.replace(/^\//, '');
|
|
11
11
|
}
|
|
12
|
+
async function readErrorMessage(response) {
|
|
13
|
+
const statusLine = `HTTP ${response.status}${response.statusText ? ` ${response.statusText}` : ''}`;
|
|
14
|
+
const contentType = response.headers.get('content-type') || '';
|
|
15
|
+
if (contentType.includes('application/json')) {
|
|
16
|
+
const payload = await response.json().catch(() => null);
|
|
17
|
+
const detail = payload?.error || payload?.message;
|
|
18
|
+
return detail ? `${statusLine}: ${detail}` : statusLine;
|
|
19
|
+
}
|
|
20
|
+
const text = (await response.text().catch(() => '')).trim();
|
|
21
|
+
return text ? `${statusLine}: ${text}` : statusLine;
|
|
22
|
+
}
|
|
12
23
|
/** 下载 zip 并解压到指定目录或 temp 目录 */
|
|
13
24
|
async function fetchAndExtractZip(url, localPath) {
|
|
14
25
|
const controller = new AbortController();
|
|
@@ -16,8 +27,7 @@ async function fetchAndExtractZip(url, localPath) {
|
|
|
16
27
|
try {
|
|
17
28
|
const response = await fetch(url, { signal: controller.signal });
|
|
18
29
|
if (!response.ok) {
|
|
19
|
-
|
|
20
|
-
throw new Error(error.error || `HTTP ${response.status}`);
|
|
30
|
+
throw new Error(await readErrorMessage(response));
|
|
21
31
|
}
|
|
22
32
|
const buffer = Buffer.from(await response.arrayBuffer());
|
|
23
33
|
const os = await import('os');
|
|
@@ -75,8 +85,7 @@ export class PpdocsApiClient {
|
|
|
75
85
|
}
|
|
76
86
|
});
|
|
77
87
|
if (!response.ok) {
|
|
78
|
-
|
|
79
|
-
throw new Error(error.error || `HTTP ${response.status}`);
|
|
88
|
+
throw new Error(await readErrorMessage(response));
|
|
80
89
|
}
|
|
81
90
|
const json = await response.json();
|
|
82
91
|
if (!json.success) {
|
|
@@ -250,7 +259,7 @@ export class PpdocsApiClient {
|
|
|
250
259
|
async updateFlowchartNode(chartId, nodeId, node, changeDesc) {
|
|
251
260
|
return this.request(`/flowcharts/${chartId}/nodes/${nodeId}`, {
|
|
252
261
|
method: 'PUT',
|
|
253
|
-
body: JSON.stringify({ node,
|
|
262
|
+
body: JSON.stringify({ node, changeDesc: changeDesc || 'MCP update' })
|
|
254
263
|
});
|
|
255
264
|
}
|
|
256
265
|
/** 原子删除单个节点 (后端自动清理关联边) */
|
|
@@ -469,9 +478,10 @@ export function initClient(apiUrl) {
|
|
|
469
478
|
}
|
|
470
479
|
export function getClient() {
|
|
471
480
|
if (!client) {
|
|
472
|
-
throw new Error('❌
|
|
473
|
-
'
|
|
474
|
-
'
|
|
481
|
+
throw new Error('❌ MCP 未连接!请检查:\n' +
|
|
482
|
+
'1. 环境变量 PPDOCS_API_URL 是否已设置\n' +
|
|
483
|
+
'2. 或当前目录下是否存在 .ppdocs 配置文件\n\n' +
|
|
484
|
+
'重新运行: npx @ppdocs/mcp init -p <projectId> -k <key>');
|
|
475
485
|
}
|
|
476
486
|
return client;
|
|
477
487
|
}
|
package/dist/tools/doc_query.js
CHANGED
|
@@ -92,13 +92,17 @@ export function registerDocQueryTools(server, _ctx) {
|
|
|
92
92
|
.slice(0, decoded.limit ?? 10);
|
|
93
93
|
if (matches.length === 0)
|
|
94
94
|
return wrap(`No docs matched: ${rawQuery}`);
|
|
95
|
-
const
|
|
95
|
+
const lines = [`Doc search: "${rawQuery}" (${matches.length} results)`, ''];
|
|
96
|
+
for (const { chart, node } of matches) {
|
|
96
97
|
const docCount = node.docEntries?.length ?? 0;
|
|
97
98
|
const latest = docCount > 0 ? node.docEntries[docCount - 1] : null;
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
99
|
+
lines.push(`- **${node.label}** [${chart.id}/${node.id}] history=${docCount}`);
|
|
100
|
+
if (latest)
|
|
101
|
+
lines.push(` current: ${shortDate(latest.date)} — ${latest.summary}`);
|
|
102
|
+
if (node.description)
|
|
103
|
+
lines.push(` desc: ${node.description.slice(0, 80)}${node.description.length > 80 ? '...' : ''}`);
|
|
104
|
+
}
|
|
105
|
+
return wrap(lines.join('\n'));
|
|
102
106
|
}
|
|
103
107
|
case 'list': {
|
|
104
108
|
const charts = decoded.chartId
|
|
@@ -109,43 +113,30 @@ export function registerDocQueryTools(server, _ctx) {
|
|
|
109
113
|
.map((n) => ({ chart, node: n })));
|
|
110
114
|
if (nodesWithDocs.length === 0)
|
|
111
115
|
return wrap('No nodes with documentation found.');
|
|
112
|
-
const
|
|
116
|
+
const lines = [`Documented nodes: ${nodesWithDocs.length}`, ''];
|
|
117
|
+
for (const { chart, node } of nodesWithDocs) {
|
|
113
118
|
const docCount = node.docEntries?.length ?? 0;
|
|
114
119
|
const latest = docCount > 0 ? node.docEntries[docCount - 1] : null;
|
|
115
|
-
const
|
|
116
|
-
|
|
117
|
-
}
|
|
118
|
-
return wrap(
|
|
120
|
+
const latestInfo = latest ? ` | ${shortDate(latest.date)} ${latest.summary}` : '';
|
|
121
|
+
lines.push(`- ${node.label} [${chart.id}/${node.id}] history=${docCount}${latestInfo}`);
|
|
122
|
+
}
|
|
123
|
+
return wrap(lines.join('\n'));
|
|
119
124
|
}
|
|
120
125
|
case 'read': {
|
|
121
126
|
if (!decoded.nodeId)
|
|
122
127
|
return wrap('read requires nodeId.');
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
if (foundChartId) {
|
|
126
|
-
chart = (await getClient().getFlowchart(foundChartId));
|
|
127
|
-
}
|
|
128
|
-
else {
|
|
129
|
-
// 不指定 chartId 时自动搜全部图
|
|
130
|
-
const all = await loadAllCharts();
|
|
131
|
-
for (const c of all) {
|
|
132
|
-
if (c.nodes.some(n => n.id === decoded.nodeId)) {
|
|
133
|
-
chart = c;
|
|
134
|
-
foundChartId = c.id;
|
|
135
|
-
break;
|
|
136
|
-
}
|
|
137
|
-
}
|
|
138
|
-
}
|
|
128
|
+
const chartId = decoded.chartId || 'main';
|
|
129
|
+
const chart = (await getClient().getFlowchart(chartId));
|
|
139
130
|
if (!chart)
|
|
140
|
-
return wrap(`
|
|
131
|
+
return wrap(`Flowchart not found: ${chartId}`);
|
|
141
132
|
const node = chart.nodes.find((n) => n.id === decoded.nodeId);
|
|
142
133
|
if (!node)
|
|
143
|
-
return wrap(`Node not found: ${decoded.nodeId} in ${
|
|
134
|
+
return wrap(`Node not found: ${decoded.nodeId} in ${chartId}`);
|
|
144
135
|
const entries = node.docEntries ?? [];
|
|
145
136
|
// history=true → 列出历史记录(标题+日期)
|
|
146
137
|
if (decoded.history) {
|
|
147
138
|
if (entries.length === 0)
|
|
148
|
-
return wrap(`No history for ${node.label} [${
|
|
139
|
+
return wrap(`No history for ${node.label} [${chartId}/${decoded.nodeId}]`);
|
|
149
140
|
const lines = [`## ${node.label} — history (${entries.length})`, ''];
|
|
150
141
|
for (let i = entries.length - 1; i >= 0; i--) {
|
|
151
142
|
const e = entries[i];
|
|
@@ -171,7 +162,7 @@ export function registerDocQueryTools(server, _ctx) {
|
|
|
171
162
|
return wrap(lines.join('\n'));
|
|
172
163
|
}
|
|
173
164
|
// 默认: 返回当前文档 (description + 最新 docEntry)
|
|
174
|
-
const lines = [`## ${node.label} [${
|
|
165
|
+
const lines = [`## ${node.label} [${chartId}/${node.id}]`, ''];
|
|
175
166
|
if (node.description) {
|
|
176
167
|
lines.push(node.description, '');
|
|
177
168
|
}
|
package/dist/tools/flowchart.js
CHANGED
|
@@ -162,7 +162,6 @@ function buildTopologyTree(chart) {
|
|
|
162
162
|
function appendDocEntry(node, summary, content) {
|
|
163
163
|
const nextNode = ensureNodeShape(node);
|
|
164
164
|
const entries = nextNode.docEntries ?? [];
|
|
165
|
-
const version = `v${entries.length + 1}.0`;
|
|
166
165
|
entries.push({
|
|
167
166
|
date: new Date().toISOString(),
|
|
168
167
|
summary: summary.replace(/\\n/g, '\n'),
|
|
@@ -170,7 +169,7 @@ function appendDocEntry(node, summary, content) {
|
|
|
170
169
|
});
|
|
171
170
|
nextNode.docEntries = entries;
|
|
172
171
|
nextNode.lastUpdated = new Date().toISOString();
|
|
173
|
-
return
|
|
172
|
+
return nextNode;
|
|
174
173
|
}
|
|
175
174
|
function collectNeighborLayers(chart, startNodeId, maxDepth) {
|
|
176
175
|
const nodeMap = new Map(chart.nodes.map((node) => [node.id, node]));
|
|
@@ -364,10 +363,8 @@ export function registerFlowchartTools(server, _ctx) {
|
|
|
364
363
|
return wrap('No flowcharts found.');
|
|
365
364
|
}
|
|
366
365
|
const lines = ['Flowcharts:', '', ...charts.map((chart) => {
|
|
367
|
-
const parent = chart.parentChart
|
|
368
|
-
|
|
369
|
-
: '';
|
|
370
|
-
return `- ${chart.title} [${chart.id}] ${chart.nodeCount}n/${chart.edgeCount}e${parent}`;
|
|
366
|
+
const parent = chart.parentChart ? ` parent=${chart.parentChart}` : '';
|
|
367
|
+
return `- ${chart.title} [${chart.id}] ${chart.nodeCount} nodes / ${chart.edgeCount} edges${parent}`;
|
|
371
368
|
})];
|
|
372
369
|
return wrap(lines.join('\n'));
|
|
373
370
|
}
|
|
@@ -396,27 +393,26 @@ export function registerFlowchartTools(server, _ctx) {
|
|
|
396
393
|
lines.push(` ${e.from} ${arrow} ${e.to}`);
|
|
397
394
|
}
|
|
398
395
|
lines.push('```');
|
|
399
|
-
// 节点简介表
|
|
396
|
+
// 节点简介表
|
|
400
397
|
lines.push('', `## Nodes (${chart.nodes.length})`, '');
|
|
401
|
-
lines.push('| 节点 | 类型 | 简介 | 绑定文件 |');
|
|
402
|
-
lines.push('|:-----|:-----|:-----|:---------|');
|
|
403
398
|
for (const n of chart.nodes) {
|
|
404
|
-
const
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
399
|
+
const meta = `${n.nodeType ?? 'process'}/${n.domain ?? 'system'}`;
|
|
400
|
+
const badges = [];
|
|
401
|
+
const fileCount = (n.boundFiles?.length ?? 0) + (n.boundDirs?.length ?? 0);
|
|
402
|
+
if (fileCount > 0)
|
|
403
|
+
badges.push(`files=${fileCount}`);
|
|
404
|
+
if (n.boundTasks?.length)
|
|
405
|
+
badges.push(`tasks=${n.boundTasks.length}`);
|
|
406
|
+
const docCount = n.docEntries?.length ?? 0;
|
|
407
|
+
if (docCount > 0)
|
|
408
|
+
badges.push(`docs=${docCount}`);
|
|
409
|
+
if (n.subFlowchart)
|
|
410
|
+
badges.push(`▶${n.subFlowchart}`);
|
|
411
|
+
const suffix = badges.length > 0 ? ` [${badges.join(' ')}]` : '';
|
|
412
|
+
const desc = n.description
|
|
413
|
+
? `\n > ${n.description.slice(0, 120)}${n.description.length > 120 ? '…' : ''}`
|
|
410
414
|
: '';
|
|
411
|
-
|
|
412
|
-
for (const f of (n.boundFiles ?? [])) {
|
|
413
|
-
fileNames.push(f.replace(/\\/g, '/').split('/').pop() ?? f);
|
|
414
|
-
}
|
|
415
|
-
for (const d of (n.boundDirs ?? [])) {
|
|
416
|
-
fileNames.push(d.replace(/\\/g, '/').split('/').pop() + '/');
|
|
417
|
-
}
|
|
418
|
-
const fileCol = fileNames.length > 0 ? fileNames.join(', ') : '-';
|
|
419
|
-
lines.push(`| ${nameCol} | ${typeCol} | ${descCol} | ${fileCol} |`);
|
|
415
|
+
lines.push(`- **${n.label}** \`${n.id}\` (${meta})${suffix}${desc}`);
|
|
420
416
|
}
|
|
421
417
|
return wrap(lines.join('\n'));
|
|
422
418
|
}
|
|
@@ -663,7 +659,6 @@ export function registerFlowchartTools(server, _ctx) {
|
|
|
663
659
|
dirs.forEach((dirPath) => lines.push(`- ${dirPath}/`));
|
|
664
660
|
}
|
|
665
661
|
}
|
|
666
|
-
// Versions 已由 docEntries 历史替代,不再输出
|
|
667
662
|
return wrap(lines.join('\n').trim());
|
|
668
663
|
}
|
|
669
664
|
case 'update_node': {
|
|
@@ -688,9 +683,8 @@ export function registerFlowchartTools(server, _ctx) {
|
|
|
688
683
|
if (decoded.description !== undefined) {
|
|
689
684
|
// 自动归档旧 description 到 docEntries
|
|
690
685
|
if (nextNode.description && nextNode.description !== decoded.description) {
|
|
691
|
-
|
|
692
|
-
|
|
693
|
-
changes.push(`docArchive(${archiveResult.version})`);
|
|
686
|
+
nextNode = appendDocEntry(nextNode, '[auto] description更新前快照', nextNode.description);
|
|
687
|
+
changes.push('docArchive');
|
|
694
688
|
}
|
|
695
689
|
nextNode.description = decoded.description;
|
|
696
690
|
changes.push('description');
|
|
@@ -719,9 +713,8 @@ export function registerFlowchartTools(server, _ctx) {
|
|
|
719
713
|
if (!decoded.docSummary || !decoded.docContent) {
|
|
720
714
|
return wrap('update_node requires both docSummary and docContent when updating docs.');
|
|
721
715
|
}
|
|
722
|
-
|
|
723
|
-
|
|
724
|
-
changes.push(`docEntries(${result.version})`);
|
|
716
|
+
nextNode = appendDocEntry(nextNode, decoded.docSummary, decoded.docContent);
|
|
717
|
+
changes.push('docEntries');
|
|
725
718
|
}
|
|
726
719
|
if (changes.length === 0) {
|
|
727
720
|
return wrap('No fields were updated.');
|
package/dist/tools/init.js
CHANGED
|
@@ -14,10 +14,24 @@ import { initClient } from '../storage/httpClient.js';
|
|
|
14
14
|
import { wrap, safeTool } from './shared.js';
|
|
15
15
|
// 当前会话的 API URL (用于幂等检查)
|
|
16
16
|
let currentApiUrl = null;
|
|
17
|
+
function findPpdocsPathFrom(dir) {
|
|
18
|
+
let currentDir = path.resolve(dir);
|
|
19
|
+
while (true) {
|
|
20
|
+
const configPath = path.join(currentDir, '.ppdocs');
|
|
21
|
+
if (fs.existsSync(configPath) && fs.statSync(configPath).isFile()) {
|
|
22
|
+
return configPath;
|
|
23
|
+
}
|
|
24
|
+
const parentDir = path.dirname(currentDir);
|
|
25
|
+
if (parentDir === currentDir) {
|
|
26
|
+
return null;
|
|
27
|
+
}
|
|
28
|
+
currentDir = parentDir;
|
|
29
|
+
}
|
|
30
|
+
}
|
|
17
31
|
/** 尝试从指定目录读取 .ppdocs 配置 */
|
|
18
32
|
function readPpdocsFrom(dir) {
|
|
19
|
-
const configPath =
|
|
20
|
-
if (!
|
|
33
|
+
const configPath = findPpdocsPathFrom(dir);
|
|
34
|
+
if (!configPath)
|
|
21
35
|
return null;
|
|
22
36
|
try {
|
|
23
37
|
const c = JSON.parse(fs.readFileSync(configPath, 'utf-8'));
|
package/package.json
CHANGED
package/templates/AGENT.md
CHANGED
package/templates/cursorrules.md
CHANGED