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.
- package/README.md +75 -50
- package/build/lib/__tests__/agents-md-template.unit.test.d.ts +1 -0
- package/build/lib/__tests__/agents-md-template.unit.test.js +25 -0
- package/build/lib/__tests__/project-context-layout.unit.test.d.ts +1 -0
- package/build/lib/__tests__/project-context-layout.unit.test.js +80 -0
- package/build/lib/agents-md-template.d.ts +25 -0
- package/build/lib/agents-md-template.js +55 -0
- package/build/lib/memory-orchestration.d.ts +3 -1
- package/build/lib/memory-orchestration.js +71 -5
- package/build/lib/merge-agents-md.d.ts +6 -0
- package/build/lib/merge-agents-md.js +51 -0
- package/build/lib/project-context-layout.d.ts +78 -0
- package/build/lib/project-context-layout.js +350 -0
- package/build/lib/workspace-root.js +6 -1
- package/build/resources/ui-ux-data/metadata.json +1 -1
- package/build/schemas/index.d.ts +25 -3
- package/build/schemas/memory-tools.d.ts +1 -1
- package/build/schemas/memory-tools.js +1 -1
- package/build/schemas/project-tools.d.ts +24 -2
- package/build/schemas/project-tools.js +24 -2
- package/build/tools/__tests__/code_insight.unit.test.js +3 -3
- package/build/tools/__tests__/init_project_context.unit.test.js +32 -21
- package/build/tools/__tests__/start_feature.unit.test.js +2 -1
- package/build/tools/code_insight.js +11 -9
- package/build/tools/init_project_context.js +563 -506
- package/build/tools/start_bugfix.js +254 -248
- package/build/tools/start_feature.js +137 -131
- package/build/tools/start_ui.js +402 -402
- package/docs/.mcp-probe/layout.json +11 -0
- package/docs/i18n/en.json +36 -5
- package/docs/i18n/ja.json +9 -2
- package/docs/i18n/ko.json +9 -2
- package/docs/i18n/zh-CN.json +36 -5
- package/docs/memory-local-setup.md +314 -0
- package/docs/memory-local-setup.zh-CN.md +280 -0
- package/docs/pages/getting-started.html +249 -31
- 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);
|
package/build/schemas/index.d.ts
CHANGED
|
@@ -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: "
|
|
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: "
|
|
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: "
|
|
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: '
|
|
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: "
|
|
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: "
|
|
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).
|
|
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).
|
|
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]).
|
|
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 {
|