mcp-probe-kit 3.0.18 → 3.0.19

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 (37) hide show
  1. package/README.md +75 -50
  2. package/build/lib/__tests__/agents-md-template.unit.test.d.ts +1 -0
  3. package/build/lib/__tests__/agents-md-template.unit.test.js +25 -0
  4. package/build/lib/__tests__/project-context-layout.unit.test.d.ts +1 -0
  5. package/build/lib/__tests__/project-context-layout.unit.test.js +80 -0
  6. package/build/lib/agents-md-template.d.ts +25 -0
  7. package/build/lib/agents-md-template.js +55 -0
  8. package/build/lib/memory-orchestration.d.ts +3 -1
  9. package/build/lib/memory-orchestration.js +71 -5
  10. package/build/lib/merge-agents-md.d.ts +6 -0
  11. package/build/lib/merge-agents-md.js +51 -0
  12. package/build/lib/project-context-layout.d.ts +78 -0
  13. package/build/lib/project-context-layout.js +350 -0
  14. package/build/lib/workspace-root.js +6 -1
  15. package/build/resources/ui-ux-data/metadata.json +1 -1
  16. package/build/schemas/index.d.ts +25 -3
  17. package/build/schemas/memory-tools.d.ts +1 -1
  18. package/build/schemas/memory-tools.js +1 -1
  19. package/build/schemas/project-tools.d.ts +24 -2
  20. package/build/schemas/project-tools.js +24 -2
  21. package/build/tools/__tests__/code_insight.unit.test.js +3 -3
  22. package/build/tools/__tests__/init_project_context.unit.test.js +32 -21
  23. package/build/tools/__tests__/start_feature.unit.test.js +2 -1
  24. package/build/tools/code_insight.js +11 -9
  25. package/build/tools/init_project_context.js +563 -506
  26. package/build/tools/start_bugfix.js +254 -248
  27. package/build/tools/start_feature.js +137 -131
  28. package/build/tools/start_ui.js +402 -402
  29. package/docs/.mcp-probe/layout.json +11 -0
  30. package/docs/i18n/en.json +36 -5
  31. package/docs/i18n/ja.json +9 -2
  32. package/docs/i18n/ko.json +9 -2
  33. package/docs/i18n/zh-CN.json +36 -5
  34. package/docs/memory-local-setup.md +314 -0
  35. package/docs/memory-local-setup.zh-CN.md +280 -0
  36. package/docs/pages/getting-started.html +249 -31
  37. package/package.json +1 -1
@@ -0,0 +1,78 @@
1
+ export declare const DEFAULT_CONTEXT_ROOT = "docs";
2
+ export declare const DEFAULT_INDEX_PATH = "AGENTS.md";
3
+ /** Relative path to layout manifest under a given context root */
4
+ export declare function layoutManifestRel(contextRoot?: string): string;
5
+ /** Default manifest location when contextRoot is `docs` */
6
+ export declare const LAYOUT_MANIFEST_REL: string;
7
+ export type IndexStyle = "auto" | "agents" | "legacy";
8
+ export type LayoutSource = "args" | "manifest" | "detect" | "default";
9
+ export type DocumentLocale = "en" | "zh-CN";
10
+ export interface ProjectContextLayout {
11
+ /** Absolute project root (platform path) */
12
+ projectRoot: string;
13
+ /** Absolute project root (POSIX, stored in layout.json) */
14
+ projectRootPosix: string;
15
+ indexPath: string;
16
+ contextRoot: string;
17
+ modularDir: string;
18
+ graphDir: string;
19
+ latestMarkdownPath: string;
20
+ latestJsonPath: string;
21
+ manifestPath: string;
22
+ indexStyle: "agents" | "legacy";
23
+ source: LayoutSource;
24
+ legacyIndexPath: string;
25
+ }
26
+ export interface ProjectContextLayoutArgs {
27
+ docs_dir?: string;
28
+ output?: string;
29
+ output_dir?: string;
30
+ filename?: string;
31
+ index_style?: IndexStyle;
32
+ }
33
+ /** Primary env key recorded in layout.json (optional local override via projectRoot) */
34
+ export declare const LAYOUT_PROJECT_ROOT_ENV = "MCP_PROJECT_ROOT";
35
+ export interface LayoutManifestV1 {
36
+ version: 1;
37
+ /** Ignored if present in old files — project root is always inferred from manifest path */
38
+ projectRoot?: string;
39
+ /** Env fallback when manifest path cannot be resolved (default MCP_PROJECT_ROOT) */
40
+ projectRootEnv?: string;
41
+ indexPath: string;
42
+ contextRoot: string;
43
+ modularDir: string;
44
+ graphDir: string;
45
+ indexStyle: "agents" | "legacy";
46
+ generatedBy: string;
47
+ generatedAt: string;
48
+ }
49
+ export declare function toPosixPath(value: string): string;
50
+ export declare function relativeLink(fromRel: string, toRel: string): string;
51
+ export declare function assertPathInsideProject(projectRoot: string, relativePath: string): void;
52
+ export declare function projectRootToManifestValue(projectRoot: string): string;
53
+ /**
54
+ * Resolve project root: infer from manifest file path → env → fallback (never reads manifest.projectRoot).
55
+ */
56
+ export declare function resolveManifestProjectRoot(manifest: LayoutManifestV1, fallbackProjectRoot: string, manifestFilePath?: string): string;
57
+ /**
58
+ * `{projectRoot}/{contextRoot}/.mcp-probe/layout.json` → walk up from manifest file to project root.
59
+ */
60
+ export declare function inferProjectRootFromManifestPath(manifestFilePath: string, contextRoot?: string): string;
61
+ export declare function findLayoutManifestPath(startDir: string): string | null;
62
+ export declare function discoverProjectRootFromLayout(startDir: string): string | null;
63
+ export declare function readLayoutManifestFromPath(manifestFilePath: string): LayoutManifestV1 | null;
64
+ export declare function readLayoutManifest(projectRoot: string): LayoutManifestV1 | null;
65
+ export declare function manifestPathRelativeToProject(projectRoot: string, absoluteManifestPath: string): string;
66
+ export declare function layoutAbsPath(layout: ProjectContextLayout, relativePath: string): string;
67
+ type ProjectContextLayoutCore = Omit<ProjectContextLayout, "projectRoot" | "projectRootPosix">;
68
+ export declare function attachProjectRoot(layout: ProjectContextLayoutCore, projectRoot: string): ProjectContextLayout;
69
+ export declare function buildLayoutManifest(layout: ProjectContextLayout): LayoutManifestV1;
70
+ export declare function writeLayoutManifest(projectRoot: string, layout: ProjectContextLayout): string;
71
+ export declare function layoutFromManifest(manifest: LayoutManifestV1, fallbackProjectRoot: string, manifestFilePath?: string): ProjectContextLayout;
72
+ export declare function resolveProjectContextLayout(projectRoot: string, args?: ProjectContextLayoutArgs): ProjectContextLayout;
73
+ export declare function countCjkChars(text: string): number;
74
+ export declare function detectDocumentLocale(projectRoot: string, existingAgentsContent?: string): DocumentLocale;
75
+ export declare function legacyProjectContextExists(projectRoot: string, layout: ProjectContextLayout): boolean;
76
+ export declare function agentsMdExists(projectRoot: string, layout: ProjectContextLayout): boolean;
77
+ export declare function parseLayoutArgsFromRecord(args: Record<string, unknown>): ProjectContextLayoutArgs;
78
+ export {};
@@ -0,0 +1,350 @@
1
+ import * as fs from "node:fs";
2
+ import * as path from "node:path";
3
+ export const DEFAULT_CONTEXT_ROOT = "docs";
4
+ export const DEFAULT_INDEX_PATH = "AGENTS.md";
5
+ /** Relative path to layout manifest under a given context root */
6
+ export function layoutManifestRel(contextRoot = DEFAULT_CONTEXT_ROOT) {
7
+ return joinRel(normalizeRelativePath(contextRoot), ".mcp-probe/layout.json");
8
+ }
9
+ /** Default manifest location when contextRoot is `docs` */
10
+ export const LAYOUT_MANIFEST_REL = layoutManifestRel(DEFAULT_CONTEXT_ROOT);
11
+ const MANIFEST_SCAN_SKIP_DIRS = new Set(["node_modules", ".git", "dist", "build", "coverage"]);
12
+ /** Primary env key recorded in layout.json (optional local override via projectRoot) */
13
+ export const LAYOUT_PROJECT_ROOT_ENV = "MCP_PROJECT_ROOT";
14
+ const LAYOUT_PROJECT_ROOT_ENV_FALLBACKS = [
15
+ LAYOUT_PROJECT_ROOT_ENV,
16
+ "MCP_WORKSPACE_ROOT",
17
+ "CURSOR_WORKSPACE_ROOT",
18
+ "WORKSPACE_ROOT",
19
+ "PROJECT_ROOT",
20
+ ];
21
+ export function toPosixPath(value) {
22
+ return value.replace(/\\/g, "/");
23
+ }
24
+ export function relativeLink(fromRel, toRel) {
25
+ const fromDir = path.posix.dirname(toPosixPath(fromRel).replace(/^\.\//, "") || ".");
26
+ const toPath = toPosixPath(toRel).replace(/^\.\//, "");
27
+ let rel = path.posix.relative(fromDir, toPath);
28
+ if (!rel || rel === ".") {
29
+ return toPath;
30
+ }
31
+ if (!rel.startsWith(".")) {
32
+ rel = `./${rel}`;
33
+ }
34
+ return rel;
35
+ }
36
+ function normalizeRelativePath(raw) {
37
+ const cleaned = toPosixPath(raw.trim()).replace(/^\/+/, "").replace(/\/+$/, "");
38
+ if (!cleaned || cleaned === ".") {
39
+ return cleaned || ".";
40
+ }
41
+ if (cleaned.split("/").includes("..")) {
42
+ throw new Error(`路径不允许包含 '..': ${raw}`);
43
+ }
44
+ return cleaned;
45
+ }
46
+ function joinRel(...segments) {
47
+ return normalizeRelativePath(path.posix.join(...segments.filter(Boolean)));
48
+ }
49
+ function isExistingDirectory(target) {
50
+ try {
51
+ return fs.existsSync(target) && fs.statSync(target).isDirectory();
52
+ }
53
+ catch {
54
+ return false;
55
+ }
56
+ }
57
+ export function assertPathInsideProject(projectRoot, relativePath) {
58
+ const resolved = path.resolve(projectRoot, relativePath);
59
+ const root = path.resolve(projectRoot);
60
+ const relative = path.relative(root, resolved);
61
+ if (relative.startsWith("..") || path.isAbsolute(relative)) {
62
+ throw new Error(`路径必须在项目根目录内: ${relativePath}`);
63
+ }
64
+ }
65
+ function fileExists(projectRoot, relativePath) {
66
+ return fs.existsSync(path.join(projectRoot, relativePath));
67
+ }
68
+ export function projectRootToManifestValue(projectRoot) {
69
+ return toPosixPath(path.resolve(projectRoot));
70
+ }
71
+ function resolveProjectRootFromEnv(envKey) {
72
+ const keys = envKey?.trim()
73
+ ? [envKey.trim(), ...LAYOUT_PROJECT_ROOT_ENV_FALLBACKS]
74
+ : [...LAYOUT_PROJECT_ROOT_ENV_FALLBACKS];
75
+ const seen = new Set();
76
+ for (const key of keys) {
77
+ if (seen.has(key)) {
78
+ continue;
79
+ }
80
+ seen.add(key);
81
+ const raw = process.env[key]?.trim();
82
+ if (!raw) {
83
+ continue;
84
+ }
85
+ const resolved = path.resolve(raw.replace(/\//g, path.sep));
86
+ if (isExistingDirectory(resolved)) {
87
+ return resolved;
88
+ }
89
+ }
90
+ return null;
91
+ }
92
+ /**
93
+ * Resolve project root: infer from manifest file path → env → fallback (never reads manifest.projectRoot).
94
+ */
95
+ export function resolveManifestProjectRoot(manifest, fallbackProjectRoot, manifestFilePath) {
96
+ if (manifestFilePath) {
97
+ const inferred = inferProjectRootFromManifestPath(manifestFilePath, manifest.contextRoot);
98
+ if (isExistingDirectory(inferred)) {
99
+ return inferred;
100
+ }
101
+ }
102
+ const fromEnv = resolveProjectRootFromEnv(manifest.projectRootEnv);
103
+ if (fromEnv) {
104
+ return fromEnv;
105
+ }
106
+ return path.resolve(fallbackProjectRoot);
107
+ }
108
+ /**
109
+ * `{projectRoot}/{contextRoot}/.mcp-probe/layout.json` → walk up from manifest file to project root.
110
+ */
111
+ export function inferProjectRootFromManifestPath(manifestFilePath, contextRoot = DEFAULT_CONTEXT_ROOT) {
112
+ let dir = path.dirname(path.resolve(manifestFilePath));
113
+ const segments = layoutManifestRel(contextRoot).split("/").filter(Boolean);
114
+ for (let i = 1; i < segments.length; i++) {
115
+ dir = path.dirname(dir);
116
+ }
117
+ return dir;
118
+ }
119
+ function findLayoutManifestInTree(dir) {
120
+ const defaultCandidate = path.join(dir, LAYOUT_MANIFEST_REL);
121
+ if (fs.existsSync(defaultCandidate)) {
122
+ return defaultCandidate;
123
+ }
124
+ try {
125
+ for (const entry of fs.readdirSync(dir, { withFileTypes: true })) {
126
+ if (!entry.isDirectory() || MANIFEST_SCAN_SKIP_DIRS.has(entry.name)) {
127
+ continue;
128
+ }
129
+ const candidate = path.join(dir, entry.name, ".mcp-probe", "layout.json");
130
+ if (fs.existsSync(candidate)) {
131
+ return candidate;
132
+ }
133
+ }
134
+ }
135
+ catch {
136
+ return null;
137
+ }
138
+ return null;
139
+ }
140
+ export function findLayoutManifestPath(startDir) {
141
+ let current = path.resolve(startDir);
142
+ while (true) {
143
+ const found = findLayoutManifestInTree(current);
144
+ if (found) {
145
+ return found;
146
+ }
147
+ const parent = path.dirname(current);
148
+ if (parent === current) {
149
+ return null;
150
+ }
151
+ current = parent;
152
+ }
153
+ }
154
+ export function discoverProjectRootFromLayout(startDir) {
155
+ const manifestPath = findLayoutManifestPath(startDir);
156
+ if (!manifestPath) {
157
+ return null;
158
+ }
159
+ const manifest = readLayoutManifestFromPath(manifestPath);
160
+ if (!manifest) {
161
+ return inferProjectRootFromManifestPath(manifestPath);
162
+ }
163
+ return resolveManifestProjectRoot(manifest, startDir, manifestPath);
164
+ }
165
+ export function readLayoutManifestFromPath(manifestFilePath) {
166
+ if (!fs.existsSync(manifestFilePath)) {
167
+ return null;
168
+ }
169
+ try {
170
+ const parsed = JSON.parse(fs.readFileSync(manifestFilePath, "utf8"));
171
+ if (parsed?.version !== 1) {
172
+ return null;
173
+ }
174
+ return parsed;
175
+ }
176
+ catch {
177
+ return null;
178
+ }
179
+ }
180
+ export function readLayoutManifest(projectRoot) {
181
+ const manifestPath = findLayoutManifestPath(projectRoot);
182
+ return manifestPath ? readLayoutManifestFromPath(manifestPath) : null;
183
+ }
184
+ export function manifestPathRelativeToProject(projectRoot, absoluteManifestPath) {
185
+ const rel = path.relative(path.resolve(projectRoot), path.resolve(absoluteManifestPath));
186
+ return toPosixPath(rel);
187
+ }
188
+ export function layoutAbsPath(layout, relativePath) {
189
+ return path.join(layout.projectRoot, normalizeRelativePath(relativePath));
190
+ }
191
+ export function attachProjectRoot(layout, projectRoot) {
192
+ const resolved = path.resolve(projectRoot);
193
+ return {
194
+ ...layout,
195
+ projectRoot: resolved,
196
+ projectRootPosix: projectRootToManifestValue(resolved),
197
+ };
198
+ }
199
+ export function buildLayoutManifest(layout) {
200
+ return {
201
+ version: 1,
202
+ projectRootEnv: LAYOUT_PROJECT_ROOT_ENV,
203
+ indexPath: layout.indexPath,
204
+ contextRoot: layout.contextRoot,
205
+ modularDir: layout.modularDir,
206
+ graphDir: layout.graphDir,
207
+ indexStyle: layout.indexStyle,
208
+ generatedBy: "init_project_context",
209
+ generatedAt: new Date().toISOString(),
210
+ };
211
+ }
212
+ export function writeLayoutManifest(projectRoot, layout) {
213
+ const resolvedRoot = path.resolve(projectRoot);
214
+ const manifestRel = layoutManifestRel(layout.contextRoot);
215
+ const manifest = buildLayoutManifest(attachProjectRoot(layout, resolvedRoot));
216
+ const absoluteManifest = path.join(resolvedRoot, ...manifestRel.split("/"));
217
+ fs.mkdirSync(path.dirname(absoluteManifest), { recursive: true });
218
+ fs.writeFileSync(absoluteManifest, `${JSON.stringify(manifest, null, 2)}\n`, "utf8");
219
+ return manifestRel;
220
+ }
221
+ export function layoutFromManifest(manifest, fallbackProjectRoot, manifestFilePath) {
222
+ const contextRoot = normalizeRelativePath(manifest.contextRoot);
223
+ const projectRoot = resolveManifestProjectRoot(manifest, fallbackProjectRoot, manifestFilePath);
224
+ const graphDir = normalizeRelativePath(manifest.graphDir);
225
+ const manifestRel = manifestFilePath
226
+ ? manifestPathRelativeToProject(projectRoot, manifestFilePath)
227
+ : layoutManifestRel(contextRoot);
228
+ return attachProjectRoot({
229
+ indexPath: normalizeRelativePath(manifest.indexPath),
230
+ contextRoot,
231
+ modularDir: normalizeRelativePath(manifest.modularDir),
232
+ graphDir,
233
+ latestMarkdownPath: joinRel(graphDir, "latest.md"),
234
+ latestJsonPath: joinRel(graphDir, "latest.json"),
235
+ manifestPath: manifestRel,
236
+ indexStyle: manifest.indexStyle,
237
+ source: "manifest",
238
+ legacyIndexPath: joinRel(contextRoot, "project-context.md"),
239
+ }, projectRoot);
240
+ }
241
+ function buildLayoutFromParts(projectRoot, contextRoot, indexPath, indexStyle, source) {
242
+ const modularDir = joinRel(contextRoot, "project-context");
243
+ const graphDir = joinRel(contextRoot, "graph-insights");
244
+ return attachProjectRoot({
245
+ indexPath,
246
+ contextRoot,
247
+ modularDir,
248
+ graphDir,
249
+ latestMarkdownPath: joinRel(graphDir, "latest.md"),
250
+ latestJsonPath: joinRel(graphDir, "latest.json"),
251
+ manifestPath: layoutManifestRel(contextRoot),
252
+ indexStyle,
253
+ source,
254
+ legacyIndexPath: joinRel(contextRoot, "project-context.md"),
255
+ }, projectRoot);
256
+ }
257
+ export function resolveProjectContextLayout(projectRoot, args = {}) {
258
+ const startRoot = path.resolve(projectRoot);
259
+ const hasExplicitArgs = Boolean(args.docs_dir?.trim() ||
260
+ args.output?.trim() ||
261
+ args.output_dir?.trim() ||
262
+ args.filename?.trim() ||
263
+ args.index_style);
264
+ const manifestPath = findLayoutManifestPath(startRoot);
265
+ const manifest = manifestPath ? readLayoutManifestFromPath(manifestPath) : readLayoutManifest(startRoot);
266
+ let canonicalRoot = startRoot;
267
+ if (!hasExplicitArgs && manifest) {
268
+ canonicalRoot = resolveManifestProjectRoot(manifest, startRoot, manifestPath ?? undefined);
269
+ }
270
+ if (!hasExplicitArgs && manifest) {
271
+ const fromManifest = layoutFromManifest(manifest, canonicalRoot, manifestPath ?? undefined);
272
+ assertPathInsideProject(fromManifest.projectRoot, fromManifest.indexPath);
273
+ assertPathInsideProject(fromManifest.projectRoot, fromManifest.contextRoot);
274
+ return fromManifest;
275
+ }
276
+ const contextRoot = normalizeRelativePath(args.docs_dir?.trim() || DEFAULT_CONTEXT_ROOT);
277
+ let indexPath;
278
+ let indexStyle;
279
+ if (args.output?.trim()) {
280
+ indexPath = normalizeRelativePath(args.output.trim());
281
+ indexStyle = indexPath.endsWith("AGENTS.md") ? "agents" : "legacy";
282
+ }
283
+ else if (args.output_dir?.trim()) {
284
+ indexPath = joinRel(args.output_dir.trim(), args.filename?.trim() || "project-context.md");
285
+ indexStyle = "legacy";
286
+ }
287
+ else if (args.index_style === "legacy") {
288
+ indexPath = joinRel(contextRoot, "project-context.md");
289
+ indexStyle = "legacy";
290
+ }
291
+ else if (args.index_style === "agents") {
292
+ indexPath = DEFAULT_INDEX_PATH;
293
+ indexStyle = "agents";
294
+ }
295
+ else {
296
+ indexPath = DEFAULT_INDEX_PATH;
297
+ indexStyle = "agents";
298
+ }
299
+ const layout = buildLayoutFromParts(canonicalRoot, contextRoot, indexPath, indexStyle, hasExplicitArgs ? "args" : manifest ? "manifest" : "default");
300
+ assertPathInsideProject(layout.projectRoot, layout.indexPath);
301
+ assertPathInsideProject(layout.projectRoot, layout.contextRoot);
302
+ assertPathInsideProject(layout.projectRoot, layout.modularDir);
303
+ assertPathInsideProject(layout.projectRoot, layout.graphDir);
304
+ return layout;
305
+ }
306
+ export function countCjkChars(text) {
307
+ return (text.match(/[\u4e00-\u9fff\u3400-\u4dbf]/g) || []).length;
308
+ }
309
+ export function detectDocumentLocale(projectRoot, existingAgentsContent) {
310
+ if (existingAgentsContent) {
311
+ const begin = existingAgentsContent.indexOf("<!-- mcp-probe:context begin");
312
+ const end = existingAgentsContent.indexOf("<!-- mcp-probe:context end -->");
313
+ if (begin !== -1 && end > begin) {
314
+ const block = existingAgentsContent.slice(begin, end);
315
+ if (countCjkChars(block) >= 8) {
316
+ return "zh-CN";
317
+ }
318
+ }
319
+ }
320
+ const readmePath = path.join(projectRoot, "README.md");
321
+ if (fs.existsSync(readmePath)) {
322
+ const sample = fs.readFileSync(readmePath, "utf8").slice(0, 4000);
323
+ const cjk = countCjkChars(sample);
324
+ const letters = (sample.match(/[a-zA-Z]/g) || []).length;
325
+ if (cjk >= 20 && cjk / Math.max(letters, 1) > 0.12) {
326
+ return "zh-CN";
327
+ }
328
+ }
329
+ if (fs.existsSync(path.join(projectRoot, "i18n", "README.zh-CN.md"))) {
330
+ return "zh-CN";
331
+ }
332
+ return "en";
333
+ }
334
+ export function legacyProjectContextExists(projectRoot, layout) {
335
+ return fileExists(layout.projectRoot, layout.legacyIndexPath);
336
+ }
337
+ export function agentsMdExists(projectRoot, layout) {
338
+ return fileExists(layout.projectRoot, layout.indexPath);
339
+ }
340
+ export function parseLayoutArgsFromRecord(args) {
341
+ return {
342
+ docs_dir: typeof args.docs_dir === "string" ? args.docs_dir : undefined,
343
+ output: typeof args.output === "string" ? args.output : undefined,
344
+ output_dir: typeof args.output_dir === "string" ? args.output_dir : undefined,
345
+ filename: typeof args.filename === "string" ? args.filename : undefined,
346
+ index_style: args.index_style === "auto" || args.index_style === "agents" || args.index_style === "legacy"
347
+ ? args.index_style
348
+ : undefined,
349
+ };
350
+ }
@@ -1,6 +1,7 @@
1
1
  import * as fs from "node:fs";
2
2
  import * as path from "node:path";
3
3
  import { fileURLToPath } from "node:url";
4
+ import { discoverProjectRootFromLayout } from "./project-context-layout.js";
4
5
  const WORKSPACE_ENV_KEYS = [
5
6
  "MCP_PROJECT_ROOT",
6
7
  "MCP_WORKSPACE_ROOT",
@@ -131,10 +132,14 @@ export function buildProjectRootRetryHint(inputPath) {
131
132
  export function resolveWorkspaceRoot(explicitProjectRoot) {
132
133
  const explicit = safeResolve(explicitProjectRoot || "");
133
134
  if (isExistingDirectory(explicit)) {
134
- return explicit;
135
+ return discoverProjectRootFromLayout(explicit) ?? explicit;
135
136
  }
136
137
  const packageRoot = getRuntimePackageRoot();
137
138
  const cwd = safeResolve(process.cwd()) || packageRoot;
139
+ const fromLayout = discoverProjectRootFromLayout(cwd);
140
+ if (fromLayout) {
141
+ return fromLayout;
142
+ }
138
143
  for (const key of WORKSPACE_ENV_KEYS) {
139
144
  const candidate = safeResolve(process.env[key] || "");
140
145
  const resolved = findWorkspaceAncestor(candidate, packageRoot);
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "version": "2.2.3",
3
- "syncedAt": "2026-05-20T09:05:15.322Z",
3
+ "syncedAt": "2026-05-26T08:30:56.969Z",
4
4
  "source": "uipro-cli",
5
5
  "format": "json"
6
6
  }
@@ -195,13 +195,35 @@ export declare const allToolSchemas: ({
195
195
  };
196
196
  } | {
197
197
  readonly name: "init_project_context";
198
- readonly description: "当用户需要生成项目上下文文档、帮助团队快速上手时使用。生成项目上下文文档(技术栈/架构/编码规范),供后续开发参考";
198
+ readonly description: "生成/更新项目上下文:默认写入 AGENTS.md(含 MCP 与 GitNexus 触发规则)及 docs/project-context/。新功能请先 start_feature,修 bug 请先 start_bugfix。完成后 Agent 应阅读 AGENTS.md。";
199
199
  readonly inputSchema: {
200
200
  readonly type: "object";
201
201
  readonly properties: {
202
202
  readonly docs_dir: {
203
203
  readonly type: "string";
204
- readonly description: "文档目录。可选,默认 docs";
204
+ readonly description: "附属文档根目录(project-context、graph-insights)。默认 docs";
205
+ };
206
+ readonly index_style: {
207
+ readonly type: "string";
208
+ readonly enum: readonly ["auto", "agents", "legacy"];
209
+ readonly description: "索引风格:auto(默认 AGENTS.md)、agents、legacy(docs/project-context.md)";
210
+ };
211
+ readonly output: {
212
+ readonly type: "string";
213
+ readonly description: "高级:索引文件相对路径,如 AGENTS.md";
214
+ };
215
+ readonly output_dir: {
216
+ readonly type: "string";
217
+ readonly description: "高级:索引所在目录,如 .claude/rules";
218
+ };
219
+ readonly filename: {
220
+ readonly type: "string";
221
+ readonly description: "高级:与 output_dir 合用,默认 project-context.md";
222
+ };
223
+ readonly locale: {
224
+ readonly type: "string";
225
+ readonly enum: readonly ["en", "zh-CN"];
226
+ readonly description: "AGENTS.md 语言;默认根据 README 探测";
205
227
  };
206
228
  };
207
229
  readonly required: readonly [];
@@ -682,7 +704,7 @@ export declare const allToolSchemas: ({
682
704
  };
683
705
  } | {
684
706
  readonly name: "memorize_asset";
685
- readonly description: " AI 完成开发并确认存在可复用代码、模式或规范时使用。将高价值资产沉淀到记忆系统。";
707
+ readonly description: "沉淀可检索资产到记忆系统:可复用代码/模式;Bug 修复后必须写入 type=bugfix(现象、根因、改法、tags 含 bugfix)。";
686
708
  readonly inputSchema: {
687
709
  readonly type: "object";
688
710
  readonly properties: {
@@ -14,7 +14,7 @@ export declare const memoryToolSchemas: readonly [{
14
14
  };
15
15
  }, {
16
16
  readonly name: "memorize_asset";
17
- readonly description: " AI 完成开发并确认存在可复用代码、模式或规范时使用。将高价值资产沉淀到记忆系统。";
17
+ readonly description: "沉淀可检索资产到记忆系统:可复用代码/模式;Bug 修复后必须写入 type=bugfix(现象、根因、改法、tags 含 bugfix)。";
18
18
  readonly inputSchema: {
19
19
  readonly type: "object";
20
20
  readonly properties: {
@@ -16,7 +16,7 @@ export const memoryToolSchemas = [
16
16
  },
17
17
  {
18
18
  name: 'memorize_asset',
19
- description: ' AI 完成开发并确认存在可复用代码、模式或规范时使用。将高价值资产沉淀到记忆系统。',
19
+ description: '沉淀可检索资产到记忆系统:可复用代码/模式;Bug 修复后必须写入 type=bugfix(现象、根因、改法、tags 含 bugfix)。',
20
20
  inputSchema: {
21
21
  type: 'object',
22
22
  properties: {
@@ -3,13 +3,35 @@
3
3
  */
4
4
  export declare const projectToolSchemas: readonly [{
5
5
  readonly name: "init_project_context";
6
- readonly description: "当用户需要生成项目上下文文档、帮助团队快速上手时使用。生成项目上下文文档(技术栈/架构/编码规范),供后续开发参考";
6
+ readonly description: "生成/更新项目上下文:默认写入 AGENTS.md(含 MCP 与 GitNexus 触发规则)及 docs/project-context/。新功能请先 start_feature,修 bug 请先 start_bugfix。完成后 Agent 应阅读 AGENTS.md。";
7
7
  readonly inputSchema: {
8
8
  readonly type: "object";
9
9
  readonly properties: {
10
10
  readonly docs_dir: {
11
11
  readonly type: "string";
12
- readonly description: "文档目录。可选,默认 docs";
12
+ readonly description: "附属文档根目录(project-context、graph-insights)。默认 docs";
13
+ };
14
+ readonly index_style: {
15
+ readonly type: "string";
16
+ readonly enum: readonly ["auto", "agents", "legacy"];
17
+ readonly description: "索引风格:auto(默认 AGENTS.md)、agents、legacy(docs/project-context.md)";
18
+ };
19
+ readonly output: {
20
+ readonly type: "string";
21
+ readonly description: "高级:索引文件相对路径,如 AGENTS.md";
22
+ };
23
+ readonly output_dir: {
24
+ readonly type: "string";
25
+ readonly description: "高级:索引所在目录,如 .claude/rules";
26
+ };
27
+ readonly filename: {
28
+ readonly type: "string";
29
+ readonly description: "高级:与 output_dir 合用,默认 project-context.md";
30
+ };
31
+ readonly locale: {
32
+ readonly type: "string";
33
+ readonly enum: readonly ["en", "zh-CN"];
34
+ readonly description: "AGENTS.md 语言;默认根据 README 探测";
13
35
  };
14
36
  };
15
37
  readonly required: readonly [];
@@ -4,13 +4,35 @@
4
4
  export const projectToolSchemas = [
5
5
  {
6
6
  name: "init_project_context",
7
- description: "当用户需要生成项目上下文文档、帮助团队快速上手时使用。生成项目上下文文档(技术栈/架构/编码规范),供后续开发参考",
7
+ description: "生成/更新项目上下文:默认写入 AGENTS.md(含 MCP 与 GitNexus 触发规则)及 docs/project-context/。新功能请先 start_feature,修 bug 请先 start_bugfix。完成后 Agent 应阅读 AGENTS.md。",
8
8
  inputSchema: {
9
9
  type: "object",
10
10
  properties: {
11
11
  docs_dir: {
12
12
  type: "string",
13
- description: "文档目录。可选,默认 docs",
13
+ description: "附属文档根目录(project-context、graph-insights)。默认 docs",
14
+ },
15
+ index_style: {
16
+ type: "string",
17
+ enum: ["auto", "agents", "legacy"],
18
+ description: "索引风格:auto(默认 AGENTS.md)、agents、legacy(docs/project-context.md)",
19
+ },
20
+ output: {
21
+ type: "string",
22
+ description: "高级:索引文件相对路径,如 AGENTS.md",
23
+ },
24
+ output_dir: {
25
+ type: "string",
26
+ description: "高级:索引所在目录,如 .claude/rules",
27
+ },
28
+ filename: {
29
+ type: "string",
30
+ description: "高级:与 output_dir 合用,默认 project-context.md",
31
+ },
32
+ locale: {
33
+ type: "string",
34
+ enum: ["en", "zh-CN"],
35
+ description: "AGENTS.md 语言;默认根据 README 探测",
14
36
  },
15
37
  },
16
38
  required: [],
@@ -144,15 +144,15 @@ describe("code_insight 单元测试", () => {
144
144
  expect(text).toMatch(/不要只口头总结而不写文件/);
145
145
  expect(text).toMatch(/docs\/graph-insights\/latest\.md/);
146
146
  expect(text).toMatch(/使用场景指南/);
147
- expect(structured.projectDocs.latestMarkdownFilePath).toContain("/docs/graph-insights/latest.md");
147
+ expect(structured.projectDocs.latestMarkdownFilePath).toMatch(/docs\/graph-insights\/latest\.md$/);
148
148
  expect(structured.projectDocs.archiveMarkdownFilePath).toContain("/docs/graph-insights/");
149
- expect(structured.projectDocs.projectContextFilePath).toContain("/docs/project-context.md");
149
+ expect(structured.projectDocs.projectContextFilePath).toMatch(/\/(AGENTS\.md|docs\/project-context\.md)$/);
150
150
  expect(structured.projectDocs.navigationSnippet).toMatch(/代码图谱洞察/);
151
151
  expect(structured.plan.mode).toBe("delegated");
152
152
  expect(structured.plan.steps).toHaveLength(2);
153
153
  expect(structured.plan.steps[0].id).toBe("consume-result");
154
154
  expect(structured.plan.steps[1].id).toBe("optional-save");
155
- expect(structured.plan.steps[1].outputs[0]).toContain("/docs/graph-insights/latest.md");
155
+ expect(structured.plan.steps[1].outputs[0]).toMatch(/docs\/graph-insights\/latest\.md$/);
156
156
  expect(fs.existsSync(path.join(projectRoot, "docs", "graph-insights", "latest.md"))).toBe(false);
157
157
  }
158
158
  finally {