mcp-probe-kit 3.6.2 → 3.6.5

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.
Files changed (30) hide show
  1. package/README.md +2 -2
  2. package/build/lib/__tests__/gitnexus-bridge.unit.test.js +1 -1
  3. package/build/lib/__tests__/project-context-writer.unit.test.d.ts +1 -0
  4. package/build/lib/__tests__/project-context-writer.unit.test.js +28 -0
  5. package/build/lib/__tests__/workspace-root.unit.test.js +16 -0
  6. package/build/lib/feature-spec-writer.d.ts +12 -0
  7. package/build/lib/feature-spec-writer.js +14 -0
  8. package/build/lib/file-delivery.d.ts +17 -0
  9. package/build/lib/file-delivery.js +42 -0
  10. package/build/lib/graph-insights-writer.d.ts +10 -0
  11. package/build/lib/graph-insights-writer.js +65 -0
  12. package/build/lib/init-project-writer.d.ts +7 -0
  13. package/build/lib/init-project-writer.js +161 -0
  14. package/build/lib/project-context-writer.d.ts +41 -0
  15. package/build/lib/project-context-writer.js +120 -0
  16. package/build/lib/tool-annotations.js +2 -2
  17. package/build/lib/workspace-root.js +18 -3
  18. package/build/schemas/output/project-tools.d.ts +136 -0
  19. package/build/schemas/output/project-tools.js +65 -1
  20. package/build/tools/__tests__/add_feature.write.unit.test.d.ts +1 -0
  21. package/build/tools/__tests__/add_feature.write.unit.test.js +30 -0
  22. package/build/tools/__tests__/code_insight.unit.test.js +2 -4
  23. package/build/tools/__tests__/init_project.write.unit.test.d.ts +1 -0
  24. package/build/tools/__tests__/init_project.write.unit.test.js +22 -0
  25. package/build/tools/__tests__/init_project_context.unit.test.js +49 -45
  26. package/build/tools/add_feature.js +23 -6
  27. package/build/tools/code_insight.js +3 -3
  28. package/build/tools/init_project.js +25 -22
  29. package/build/tools/init_project_context.js +66 -44
  30. package/package.json +1 -1
package/README.md CHANGED
@@ -421,7 +421,7 @@ No installation needed, use the latest version directly.
421
421
  }
422
422
  ```
423
423
 
424
- > **Skill 自动安装**:任意 MCP 工具调用会在用户项目写入 `.agents/skills/mcp-probe-kit/SKILL.md`。工作区根目录**自动识别**(Cursor 注入 `WORKSPACE_FOLDER_PATHS`;OpenCode 等项目级 `opencode.json` 会设置进程 cwd),**无需**在每台客户端配置 `MCP_PROJECT_ROOT`。仅全局 MCP 且无法识别工作区时,可选手动设置 `MCP_PROJECT_ROOT` 或在工具参数传 `project_root`。需 **v3.6.2+**。
424
+ > **Skill 自动安装**:任意 MCP 工具调用会在用户项目写入 `.agents/skills/mcp-probe-kit/SKILL.md`。工作区根目录**自动识别**(Cursor 注入 `WORKSPACE_FOLDER_PATHS`;OpenCode 等项目级 `opencode.json` 会设置进程 cwd),**无需**在每台客户端配置 `MCP_PROJECT_ROOT`。仅全局 MCP 且无法识别工作区时,可选手动设置 `MCP_PROJECT_ROOT` 或在工具参数传 `project_root`。需 **v3.6.3+**。
425
425
 
426
426
  #### Claude Desktop Configuration
427
427
 
@@ -712,7 +712,7 @@ This is a known [Cursor-side issue](https://forum.cursor.com/t/mcp-server-connec
712
712
  | `latched shared-process MCP routing disabled` + `ipcReady` timeout | Windows `mcpProcess` utility failed; legacy fallback discovers tools but Agent lease stays empty |
713
713
  | Settings green dot, Agent `No MCP servers available` | Renderer ↔ shared-process MCP routing not wired for this session |
714
714
 
715
- **What we do (v3.6.2+):** `tools/list` **omits `outputSchema` by default** (~50 KB → ~23 KB). Structured output still works via `structuredContent` on `tools/call`. To restore full schemas: `MCP_INCLUDE_OUTPUT_SCHEMA=1`.
715
+ **What we do (v3.6.3+):** `tools/list` **omits `outputSchema` by default** (~50 KB → ~23 KB). Structured output still works via `structuredContent` on `tools/call`. To restore full schemas: `MCP_INCLUDE_OUTPUT_SCHEMA=1`.
716
716
 
717
717
  **What you can try:**
718
718
 
@@ -116,7 +116,7 @@ describe("gitnexus-bridge workspace preparation", () => {
116
116
  expect(result.code).toBe(0);
117
117
  expect(result.stdout.trim().length).toBeGreaterThan(0);
118
118
  });
119
- test("Windows 下 git.exe 直接启动,不走 git.cmd 壳层", () => {
119
+ test.runIf(process.platform === "win32")("Windows 下 git.exe 直接启动,不走 git.cmd 壳层", () => {
120
120
  const resolved = resolveExecutableCommand("git", "win32").toLowerCase();
121
121
  const spawned = resolveSpawnCommand("git", ["init", "-q"], "win32");
122
122
  expect(resolved).toContain("git");
@@ -0,0 +1,28 @@
1
+ import fs from "node:fs";
2
+ import os from "node:os";
3
+ import path from "node:path";
4
+ import { describe, expect, test } from "vitest";
5
+ import { resolveProjectContextLayout } from "../project-context-layout.js";
6
+ import { writeProjectContextArtifacts } from "../project-context-writer.js";
7
+ describe("project-context-writer", () => {
8
+ test("写入 AGENTS.md 与 modular 骨架", () => {
9
+ const projectRoot = fs.mkdtempSync(path.join(os.tmpdir(), "mcp-probe-writer-"));
10
+ const layout = resolveProjectContextLayout(projectRoot, { docs_dir: "docs" });
11
+ const docs = [
12
+ { file: "tech-stack.md", title: "技术栈", purpose: "技术栈说明" },
13
+ { file: "architecture.md", title: "架构设计", purpose: "架构说明" },
14
+ ];
15
+ const result = writeProjectContextArtifacts({
16
+ layout,
17
+ mergedAgentsContent: "# AGENTS\n",
18
+ skipModularDocs: false,
19
+ projectInfo: { name: "demo", version: "1.0.0", description: "test" },
20
+ detection: { language: "TypeScript", framework: "node", category: "library" },
21
+ docs,
22
+ });
23
+ expect(result.agentsMd.action).toBe("created");
24
+ expect(fs.existsSync(path.join(projectRoot, "AGENTS.md"))).toBe(true);
25
+ expect(fs.existsSync(path.join(projectRoot, "docs", "project-context.md"))).toBe(true);
26
+ expect(fs.existsSync(path.join(projectRoot, "docs", "project-context", "tech-stack.md"))).toBe(true);
27
+ });
28
+ });
@@ -60,4 +60,20 @@ describe("workspace-root explicit project_root", () => {
60
60
  expect(resolution.explicitHonored).toBe(false);
61
61
  expect(resolution.warning).toMatch(/未能采用传入的 project_root/);
62
62
  });
63
+ test("不把盘符根目录当作工作区", () => {
64
+ const original = process.env.INIT_CWD;
65
+ process.env.INIT_CWD = "D:\\";
66
+ try {
67
+ const resolution = resolveWorkspaceRootWithMeta("");
68
+ expect(resolution.root).not.toBe("D:\\");
69
+ }
70
+ finally {
71
+ if (original === undefined) {
72
+ delete process.env.INIT_CWD;
73
+ }
74
+ else {
75
+ process.env.INIT_CWD = original;
76
+ }
77
+ }
78
+ });
63
79
  });
@@ -0,0 +1,12 @@
1
+ import { type FileDeliveryReport } from "./file-delivery.js";
2
+ export interface FeatureSpecTemplates {
3
+ requirements: string;
4
+ design: string;
5
+ tasks: string;
6
+ }
7
+ export declare function writeFeatureSpecFiles(input: {
8
+ projectRoot: string;
9
+ docsDir: string;
10
+ featureName: string;
11
+ templates: FeatureSpecTemplates;
12
+ }): FileDeliveryReport;
@@ -0,0 +1,14 @@
1
+ import { mergeDeliveryReports, writeProjectFile, } from "./file-delivery.js";
2
+ export function writeFeatureSpecFiles(input) {
3
+ const base = `${input.docsDir}/specs/${input.featureName}`;
4
+ const root = input.projectRoot;
5
+ const report = mergeDeliveryReports({
6
+ writtenFiles: [
7
+ writeProjectFile(root, `${base}/requirements.md`, input.templates.requirements, "ifMissing"),
8
+ writeProjectFile(root, `${base}/design.md`, input.templates.design, "ifMissing"),
9
+ writeProjectFile(root, `${base}/tasks.md`, input.templates.tasks, "ifMissing"),
10
+ ],
11
+ pendingFiles: [],
12
+ });
13
+ return report;
14
+ }
@@ -0,0 +1,17 @@
1
+ export type FileWriteAction = "created" | "updated" | "skipped";
2
+ export interface DeliveredFile {
3
+ path: string;
4
+ action: FileWriteAction;
5
+ }
6
+ export interface PendingFile {
7
+ path: string;
8
+ reason: string;
9
+ }
10
+ export interface FileDeliveryReport {
11
+ writtenFiles: DeliveredFile[];
12
+ pendingFiles: PendingFile[];
13
+ }
14
+ export declare function toPosixRel(relPath: string): string;
15
+ export declare function writeProjectFile(projectRoot: string, relPath: string, content: string, policy: "ifMissing" | "always"): DeliveredFile;
16
+ export declare function mergeDeliveryReports(...reports: FileDeliveryReport[]): FileDeliveryReport;
17
+ export declare function formatFileDeliverySection(report: FileDeliveryReport): string;
@@ -0,0 +1,42 @@
1
+ import * as fs from "node:fs";
2
+ import * as path from "node:path";
3
+ export function toPosixRel(relPath) {
4
+ return relPath.replace(/\\/g, "/");
5
+ }
6
+ export function writeProjectFile(projectRoot, relPath, content, policy) {
7
+ const posixPath = toPosixRel(relPath);
8
+ const absPath = path.join(path.resolve(projectRoot), ...posixPath.split("/"));
9
+ const existed = fs.existsSync(absPath);
10
+ if (policy === "ifMissing" && existed) {
11
+ return { path: posixPath, action: "skipped" };
12
+ }
13
+ fs.mkdirSync(path.dirname(absPath), { recursive: true });
14
+ fs.writeFileSync(absPath, content, "utf8");
15
+ return { path: posixPath, action: existed ? "updated" : "created" };
16
+ }
17
+ export function mergeDeliveryReports(...reports) {
18
+ return {
19
+ writtenFiles: reports.flatMap((report) => report.writtenFiles),
20
+ pendingFiles: reports.flatMap((report) => report.pendingFiles),
21
+ };
22
+ }
23
+ export function formatFileDeliverySection(report) {
24
+ const lines = ["## 文件落盘状态"];
25
+ if (report.writtenFiles.length > 0) {
26
+ lines.push("", "### 已写入(MCP 服务端)");
27
+ for (const file of report.writtenFiles) {
28
+ const verb = file.action === "created" ? "已创建" : file.action === "updated" ? "已更新" : "已跳过(已存在)";
29
+ lines.push(`- \`${file.path}\` — ${verb}`);
30
+ }
31
+ }
32
+ if (report.pendingFiles.length > 0) {
33
+ lines.push("", "### 尚未创建(仅规划 / 待后续步骤)");
34
+ for (const file of report.pendingFiles) {
35
+ lines.push(`- \`${file.path}\` — ${file.reason}`);
36
+ }
37
+ }
38
+ if (report.writtenFiles.length === 0 && report.pendingFiles.length === 0) {
39
+ lines.push("", "- 无文件变更");
40
+ }
41
+ return lines.join("\n");
42
+ }
@@ -0,0 +1,10 @@
1
+ import type { CodeInsightBridgeResult } from "./gitnexus-bridge.js";
2
+ import { type ProjectContextLayout } from "./project-context-layout.js";
3
+ import { type FileDeliveryReport } from "./file-delivery.js";
4
+ export declare function buildGraphInsightMarkdown(result: CodeInsightBridgeResult, structured: Record<string, unknown>): string;
5
+ export declare function persistGraphInsightsDocs(input: {
6
+ projectRoot: string;
7
+ layout: ProjectContextLayout;
8
+ result: CodeInsightBridgeResult;
9
+ structured: Record<string, unknown>;
10
+ }): FileDeliveryReport;
@@ -0,0 +1,65 @@
1
+ import * as fs from "node:fs";
2
+ import * as path from "node:path";
3
+ import { layoutAbsPath } from "./project-context-layout.js";
4
+ import { mergeDeliveryReports, toPosixRel, writeProjectFile, } from "./file-delivery.js";
5
+ function makeSafeSegment(value) {
6
+ return (value || "code-insight")
7
+ .toLowerCase()
8
+ .replace(/[^a-z0-9._-]+/g, "-")
9
+ .replace(/-+/g, "-")
10
+ .replace(/^-|-$/g, "")
11
+ .slice(0, 48) || "code-insight";
12
+ }
13
+ export function buildGraphInsightMarkdown(result, structured) {
14
+ const mode = structured.mode;
15
+ const executionLines = result.executions
16
+ .map((item) => {
17
+ if (item.ok) {
18
+ return `- **${item.tool}**${item.status ? ` [${item.status}]` : ""}: ${(item.text || "已返回结果").replace(/\s+/g, " ").slice(0, 500)}`;
19
+ }
20
+ return `- **${item.tool}** ⚠️: ${(item.error || "调用失败").replace(/\s+/g, " ").slice(0, 300)}`;
21
+ })
22
+ .join("\n");
23
+ return `# 代码图谱洞察
24
+
25
+ > 由 mcp-probe-kit \`code_insight\` 自动生成
26
+
27
+ | 字段 | 值 |
28
+ |------|-----|
29
+ | 状态 | ${String(structured.status ?? "ok")} |
30
+ | 模式 | ${mode?.requested ?? "auto"} → ${mode?.resolved ?? "auto"} |
31
+ | 来源 | ${result.provider} |
32
+ | 工作区 | ${result.workspaceMode} |
33
+ | 生成时间 | ${new Date().toISOString()} |
34
+
35
+ ## 摘要
36
+
37
+ ${result.summary}
38
+
39
+ ## 执行记录
40
+
41
+ ${executionLines || "- 无执行记录"}
42
+
43
+ ${result.warnings.length > 0 ? `\n## 警告\n\n${result.warnings.map((w) => `- ${w}`).join("\n")}\n` : ""}`;
44
+ }
45
+ export function persistGraphInsightsDocs(input) {
46
+ const markdown = buildGraphInsightMarkdown(input.result, input.structured);
47
+ const json = JSON.stringify(input.structured, null, 2);
48
+ const root = path.resolve(input.projectRoot);
49
+ const latestMd = writeProjectFile(root, input.layout.latestMarkdownPath, markdown, "always");
50
+ const latestJson = writeProjectFile(root, input.layout.latestJsonPath, json, "always");
51
+ const graphAbs = layoutAbsPath(input.layout, input.layout.graphDir);
52
+ fs.mkdirSync(graphAbs, { recursive: true });
53
+ const timestamp = new Date().toISOString().replace(/[:.]/g, "-");
54
+ const suffix = makeSafeSegment(input.result.summary || String(input.structured.status ?? "ok"));
55
+ const mode = input.structured.mode?.resolved ?? "auto";
56
+ const archiveBase = `${timestamp}-${mode}-${suffix}`;
57
+ const archiveMdRel = toPosixRel(path.join(input.layout.graphDir, `${archiveBase}.md`));
58
+ const archiveJsonRel = toPosixRel(path.join(input.layout.graphDir, `${archiveBase}.json`));
59
+ const archiveMd = writeProjectFile(root, archiveMdRel, markdown, "always");
60
+ const archiveJson = writeProjectFile(root, archiveJsonRel, json, "always");
61
+ return mergeDeliveryReports({
62
+ writtenFiles: [latestMd, latestJson, archiveMd, archiveJson],
63
+ pendingFiles: [],
64
+ });
65
+ }
@@ -0,0 +1,7 @@
1
+ import { type FileDeliveryReport } from "./file-delivery.js";
2
+ export declare function writeInitProjectSkeletons(input: {
3
+ projectRoot: string;
4
+ projectName: string;
5
+ featureSlug: string;
6
+ requirement: string;
7
+ }): FileDeliveryReport;
@@ -0,0 +1,161 @@
1
+ import { mergeDeliveryReports, writeProjectFile, } from "./file-delivery.js";
2
+ export function writeInitProjectSkeletons(input) {
3
+ const { projectRoot, projectName, featureSlug, requirement } = input;
4
+ const specBase = `docs/specs/${featureSlug}`;
5
+ const projectContext = `# ${projectName} - 项目上下文
6
+
7
+ > 由 init_project 自动生成的骨架,请根据仓库实际情况补充。
8
+
9
+ ## 项目概览
10
+
11
+ | 属性 | 值 |
12
+ |------|-----|
13
+ | 项目名称 | ${projectName} |
14
+ | 描述 | ${requirement || "待补充"} |
15
+
16
+ ## 技术栈
17
+
18
+ - 待补充
19
+
20
+ ## 架构
21
+
22
+ - 待补充
23
+
24
+ ## 开发流程
25
+
26
+ - 待补充
27
+
28
+ ---
29
+ *生成工具: mcp-probe-kit init_project*
30
+ `;
31
+ const constitution = `# ${projectName} - 项目宪法
32
+
33
+ > 定义项目核心原则与约束。
34
+
35
+ ## 代码风格
36
+
37
+ - 待补充
38
+
39
+ ## 架构原则
40
+
41
+ - 待补充
42
+
43
+ ## 安全准则
44
+
45
+ - 待补充
46
+
47
+ ## 测试要求
48
+
49
+ - 待补充
50
+
51
+ ## 文档标准
52
+
53
+ - 待补充
54
+
55
+ ---
56
+ *生成工具: mcp-probe-kit init_project*
57
+ `;
58
+ const requirements = `# 需求文档:${projectName}
59
+
60
+ ## 功能概述
61
+
62
+ ${requirement || "待补充"}
63
+
64
+ ## 术语定义
65
+
66
+ - 待补充
67
+
68
+ ## 需求列表(EARS)
69
+
70
+ ### 用户故事
71
+
72
+ - As a ... I want ... So that ...
73
+
74
+ ### 验收标准
75
+
76
+ - WHEN ... THEN THE system SHALL ...
77
+
78
+ ## 非功能需求
79
+
80
+ - 性能、安全、兼容性:待补充
81
+
82
+ ---
83
+ *生成工具: mcp-probe-kit init_project*
84
+ `;
85
+ const design = `# 设计文档:${projectName}
86
+
87
+ ## 技术方案
88
+
89
+ - 待补充
90
+
91
+ ## 架构设计
92
+
93
+ - 待补充
94
+
95
+ ## 数据模型
96
+
97
+ - 待补充
98
+
99
+ ## API 设计
100
+
101
+ - 待补充
102
+
103
+ ---
104
+ *生成工具: mcp-probe-kit init_project*
105
+ `;
106
+ const tasks = `# 任务清单:${projectName}
107
+
108
+ ## 概述
109
+
110
+ 实现 ${projectName} 的任务分解。
111
+
112
+ ## 任务列表
113
+
114
+ ### 阶段 1: 项目初始化
115
+
116
+ - [ ] 1.1 搭建项目骨架
117
+
118
+ ### 阶段 2: 核心功能
119
+
120
+ - [ ] 2.1 实现核心逻辑
121
+
122
+ ### 阶段 3: 测试
123
+
124
+ - [ ] 3.1 编写测试
125
+
126
+ ---
127
+ *生成工具: mcp-probe-kit init_project*
128
+ `;
129
+ const research = `# 技术调研:${projectName}
130
+
131
+ ## 技术栈调研
132
+
133
+ - 待补充
134
+
135
+ ## 版本兼容性
136
+
137
+ - 待补充
138
+
139
+ ## 风险与最佳实践
140
+
141
+ - 待补充
142
+
143
+ ---
144
+ *生成工具: mcp-probe-kit init_project*
145
+ `;
146
+ return mergeDeliveryReports({
147
+ writtenFiles: [
148
+ writeProjectFile(projectRoot, "docs/project-context.md", projectContext, "ifMissing"),
149
+ writeProjectFile(projectRoot, "docs/constitution.md", constitution, "ifMissing"),
150
+ writeProjectFile(projectRoot, `${specBase}/requirements.md`, requirements, "ifMissing"),
151
+ writeProjectFile(projectRoot, `${specBase}/design.md`, design, "ifMissing"),
152
+ writeProjectFile(projectRoot, `${specBase}/tasks.md`, tasks, "ifMissing"),
153
+ writeProjectFile(projectRoot, `${specBase}/research.md`, research, "ifMissing"),
154
+ ],
155
+ pendingFiles: [
156
+ { path: "scripts/check-prerequisites.sh", reason: "需 Agent 按指南创建辅助脚本" },
157
+ { path: "scripts/setup.sh", reason: "需 Agent 按指南创建辅助脚本" },
158
+ { path: "src/", reason: "源代码目录需按项目类型创建" },
159
+ ],
160
+ });
161
+ }
@@ -0,0 +1,41 @@
1
+ import type { ProjectContextLayout } from "./project-context-layout.js";
2
+ export interface WrittenFileRecord {
3
+ path: string;
4
+ action: "created" | "updated" | "skipped";
5
+ }
6
+ export interface ProjectContextWriteResult {
7
+ agentsMd: WrittenFileRecord;
8
+ projectContextIndex: WrittenFileRecord | null;
9
+ modularDocs: WrittenFileRecord[];
10
+ }
11
+ type DocEntry = {
12
+ file: string;
13
+ title: string;
14
+ purpose: string;
15
+ };
16
+ type ProjectInfo = {
17
+ name: string;
18
+ version: string;
19
+ description: string;
20
+ };
21
+ type Detection = {
22
+ language: string;
23
+ framework?: string;
24
+ category: string;
25
+ confidence?: number;
26
+ };
27
+ export declare function buildProjectContextIndexMarkdown(projectInfo: ProjectInfo, detection: Detection, docs: DocEntry[], docsDir: string): string;
28
+ export declare function buildModularDocSkeleton(doc: DocEntry, projectInfo: ProjectInfo, detection: Detection): string;
29
+ /**
30
+ * 由 init_project_context 服务端直接落盘(勿依赖 Agent fsWrite)。
31
+ */
32
+ export declare function writeProjectContextArtifacts(input: {
33
+ layout: ProjectContextLayout;
34
+ mergedAgentsContent: string;
35
+ skipModularDocs: boolean;
36
+ projectInfo: ProjectInfo;
37
+ detection: Detection;
38
+ docs: DocEntry[];
39
+ }): ProjectContextWriteResult;
40
+ export declare function formatWriteSummary(result: ProjectContextWriteResult): string;
41
+ export {};
@@ -0,0 +1,120 @@
1
+ import * as fs from "node:fs";
2
+ import * as path from "node:path";
3
+ import { layoutAbsPath } from "./project-context-layout.js";
4
+ function writeFileEnsuringDir(absPath, content) {
5
+ fs.mkdirSync(path.dirname(absPath), { recursive: true });
6
+ fs.writeFileSync(absPath, content, "utf8");
7
+ }
8
+ function writeIfMissing(layout, relPath, content) {
9
+ const absPath = layoutAbsPath(layout, relPath);
10
+ if (fs.existsSync(absPath)) {
11
+ return { path: relPath, action: "skipped" };
12
+ }
13
+ writeFileEnsuringDir(absPath, content);
14
+ return { path: relPath, action: "created" };
15
+ }
16
+ function writeAlways(layout, relPath, content) {
17
+ const absPath = layoutAbsPath(layout, relPath);
18
+ const existed = fs.existsSync(absPath);
19
+ writeFileEnsuringDir(absPath, content);
20
+ return { path: relPath, action: existed ? "updated" : "created" };
21
+ }
22
+ export function buildProjectContextIndexMarkdown(projectInfo, detection, docs, docsDir) {
23
+ const timestamp = new Date().toISOString();
24
+ const docNav = docs
25
+ .map((doc) => `### [${doc.title}](./project-context/${doc.file})
26
+ ${doc.purpose}
27
+ `)
28
+ .join("\n");
29
+ return `# ${projectInfo.name} - 项目上下文
30
+
31
+ > 本文档是项目上下文的索引文件,提供项目概览和文档导航。
32
+
33
+ ## 📊 项目概览
34
+
35
+ | 属性 | 值 |
36
+ |------|-----|
37
+ | 项目名称 | ${projectInfo.name} |
38
+ | 版本 | ${projectInfo.version} |
39
+ | 语言 | ${detection.language} |
40
+ | 框架 | ${detection.framework || "无"} |
41
+ | 类型 | ${detection.category} |
42
+ | 描述 | ${projectInfo.description || "待补充"} |
43
+
44
+ ## 📚 文档导航
45
+
46
+ ${docNav}
47
+ ### [代码图谱洞察](./graph-insights/latest.md)
48
+ 最近一次 code_insight 分析结果,包含模块依赖、调用链和影响面摘要
49
+
50
+ ## 🚀 快速开始
51
+
52
+ 1. 阅读 [技术栈](./project-context/tech-stack.md) 了解项目使用的技术
53
+ 2. 阅读 [架构设计](./project-context/architecture.md) 了解项目结构
54
+ 3. 阅读 [代码图谱洞察](./graph-insights/latest.md) 快速理解模块依赖与调用链
55
+ 4. 根据需要查看具体的操作指南
56
+
57
+ ---
58
+ *生成时间: ${timestamp}*
59
+ *生成工具: mcp-probe-kit init_project_context*
60
+ `;
61
+ }
62
+ export function buildModularDocSkeleton(doc, projectInfo, detection) {
63
+ return `# ${doc.title}
64
+
65
+ > ${doc.purpose}
66
+
67
+ ## 概述
68
+
69
+ 本文档描述 **${projectInfo.name}** 的${doc.title}。请根据仓库实际代码补充下方各节(勿留空占位)。
70
+
71
+ ## 项目信息
72
+
73
+ | 属性 | 值 |
74
+ |------|-----|
75
+ | 语言 | ${detection.language} |
76
+ | 框架 | ${detection.framework || "无"} |
77
+ | 类型 | ${detection.category} |
78
+
79
+ ## 待补充
80
+
81
+ - 从项目中提取真实路径与代码示例
82
+ - 步骤需具体可操作
83
+
84
+ ---
85
+ *返回索引: [../project-context.md](../project-context.md)*
86
+ `;
87
+ }
88
+ /**
89
+ * 由 init_project_context 服务端直接落盘(勿依赖 Agent fsWrite)。
90
+ */
91
+ export function writeProjectContextArtifacts(input) {
92
+ const { layout, mergedAgentsContent, skipModularDocs, projectInfo, detection, docs } = input;
93
+ const docsDir = layout.contextRoot;
94
+ const agentsMd = writeAlways(layout, layout.indexPath, mergedAgentsContent);
95
+ let projectContextIndex = null;
96
+ const modularDocs = [];
97
+ if (!skipModularDocs) {
98
+ projectContextIndex = writeIfMissing(layout, layout.legacyIndexPath, buildProjectContextIndexMarkdown(projectInfo, detection, docs, docsDir));
99
+ for (const doc of docs) {
100
+ const rel = `${layout.modularDir}/${doc.file}`;
101
+ modularDocs.push(writeIfMissing(layout, rel, buildModularDocSkeleton(doc, projectInfo, detection)));
102
+ }
103
+ }
104
+ return { agentsMd, projectContextIndex, modularDocs };
105
+ }
106
+ export function formatWriteSummary(result) {
107
+ const lines = ["## 已写入磁盘(MCP 服务端)"];
108
+ const push = (record) => {
109
+ const verb = record.action === "created" ? "已创建" : record.action === "updated" ? "已更新" : "已跳过(已存在)";
110
+ lines.push(`- \`${record.path}\` — ${verb}`);
111
+ };
112
+ push(result.agentsMd);
113
+ if (result.projectContextIndex) {
114
+ push(result.projectContextIndex);
115
+ }
116
+ for (const doc of result.modularDocs) {
117
+ push(doc);
118
+ }
119
+ return lines.join("\n");
120
+ }
@@ -14,7 +14,7 @@
14
14
  */
15
15
  export const TOOL_ANNOTATIONS = {
16
16
  // —— 只读指南型(可安全自动放行)——
17
- init_project: { title: '初始化项目', readOnlyHint: true, idempotentHint: true, openWorldHint: false },
17
+ init_project: { title: '初始化项目', readOnlyHint: false, idempotentHint: true, openWorldHint: false }, // 仅写 Skill + AGENTS.md
18
18
  workflow: { title: '开发工作流路由', readOnlyHint: true, idempotentHint: true, openWorldHint: false },
19
19
  gencommit: { title: '生成提交信息', readOnlyHint: true, idempotentHint: true, openWorldHint: false },
20
20
  code_review: { title: '代码审查', readOnlyHint: true, idempotentHint: true, openWorldHint: false },
@@ -43,7 +43,7 @@ export const TOOL_ANNOTATIONS = {
43
43
  start_feature: { title: '新功能开发编排', readOnlyHint: true, idempotentHint: false, openWorldHint: true },
44
44
  start_bugfix: { title: 'Bug 修复编排', readOnlyHint: true, idempotentHint: false, openWorldHint: true },
45
45
  // —— 写型(工具自身落盘 / 写记忆 / 写缓存,非破坏)——
46
- init_project_context: { title: '生成项目上下文', readOnlyHint: false, destructiveHint: false, idempotentHint: true, openWorldHint: false }, // 自己写 docs/.mcp-probe/layout.json(其余为指令)
46
+ init_project_context: { title: '生成项目上下文', readOnlyHint: false, destructiveHint: false, idempotentHint: true, openWorldHint: false }, // 仅写 AGENTS.md + layout.json
47
47
  memorize_asset: { title: '沉淀记忆资产', readOnlyHint: false, destructiveHint: false, idempotentHint: true, openWorldHint: true }, // 写 Qdrant
48
48
  delete_memory_asset: { title: '删除记忆资产', readOnlyHint: false, destructiveHint: true, idempotentHint: true, openWorldHint: true }, // 删 Qdrant
49
49
  update_memory_asset: { title: '更新记忆资产', readOnlyHint: false, destructiveHint: false, idempotentHint: true, openWorldHint: true }, // 写 Qdrant
@@ -126,7 +126,9 @@ function findWorkspaceAncestor(start, packageRoot) {
126
126
  }
127
127
  let current = start;
128
128
  while (true) {
129
- if (current !== packageRoot && looksLikeWorkspaceRoot(current)) {
129
+ if (current !== packageRoot &&
130
+ !isFilesystemRoot(current) &&
131
+ looksLikeWorkspaceRoot(current)) {
130
132
  return current;
131
133
  }
132
134
  const parent = path.dirname(current);
@@ -146,6 +148,19 @@ function resolveTrustedClientWorkspace(clientPath, packageRoot) {
146
148
  }
147
149
  return findWorkspaceAncestor(clientPath, packageRoot) ?? clientPath;
148
150
  }
151
+ function isFilesystemRoot(target) {
152
+ const normalized = path.resolve(target);
153
+ return normalized === path.parse(normalized).root;
154
+ }
155
+ function isUsableWorkspaceCandidate(target, packageRoot) {
156
+ if (!isExistingDirectory(target)) {
157
+ return false;
158
+ }
159
+ if (target === packageRoot || isFilesystemRoot(target)) {
160
+ return false;
161
+ }
162
+ return true;
163
+ }
149
164
  function resolveFromEnvKeys(keys, packageRoot) {
150
165
  for (const key of keys) {
151
166
  const candidate = safeResolve(process.env[key] || "");
@@ -153,7 +168,7 @@ function resolveFromEnvKeys(keys, packageRoot) {
153
168
  if (resolved) {
154
169
  return resolved;
155
170
  }
156
- if (isExistingDirectory(candidate) && candidate !== packageRoot) {
171
+ if (isUsableWorkspaceCandidate(candidate, packageRoot)) {
157
172
  return candidate;
158
173
  }
159
174
  }
@@ -219,7 +234,7 @@ function resolveAutoWorkspaceRoot(packageRoot) {
219
234
  };
220
235
  }
221
236
  const cwd = safeResolve(process.cwd());
222
- if (isExistingDirectory(cwd) && cwd !== packageRoot) {
237
+ if (isUsableWorkspaceCandidate(cwd, packageRoot)) {
223
238
  if (looksLikeWorkspaceRoot(cwd)) {
224
239
  return { root: cwd, source: "cwd", explicitHonored: false };
225
240
  }