mcp-probe-kit 3.6.5 → 3.6.9

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 (45) hide show
  1. package/README.md +3 -1
  2. package/build/index.js +1 -0
  3. package/build/lib/__tests__/agents-md-template.unit.test.js +2 -0
  4. package/build/lib/__tests__/agents-skill-ref.unit.test.d.ts +1 -0
  5. package/build/lib/__tests__/agents-skill-ref.unit.test.js +32 -0
  6. package/build/lib/__tests__/file-delivery.unit.test.d.ts +1 -0
  7. package/build/lib/__tests__/file-delivery.unit.test.js +62 -0
  8. package/build/lib/__tests__/harness-adapters.unit.test.d.ts +1 -0
  9. package/build/lib/__tests__/harness-adapters.unit.test.js +68 -0
  10. package/build/lib/__tests__/harness-skill-targets.unit.test.d.ts +1 -0
  11. package/build/lib/__tests__/harness-skill-targets.unit.test.js +59 -0
  12. package/build/lib/__tests__/project-mcp-resources.unit.test.js +1 -1
  13. package/build/lib/__tests__/workflow-skill-frontmatter.unit.test.d.ts +1 -0
  14. package/build/lib/__tests__/workflow-skill-frontmatter.unit.test.js +31 -0
  15. package/build/lib/__tests__/workflow-skill-installer.unit.test.js +21 -6
  16. package/build/lib/agents-md-template.d.ts +1 -0
  17. package/build/lib/agents-md-template.js +21 -6
  18. package/build/lib/agents-skill-ref.d.ts +6 -0
  19. package/build/lib/agents-skill-ref.js +45 -0
  20. package/build/lib/file-delivery.d.ts +11 -0
  21. package/build/lib/file-delivery.js +20 -0
  22. package/build/lib/harness-adapters.d.ts +19 -0
  23. package/build/lib/harness-adapters.js +166 -0
  24. package/build/lib/harness-skill-targets.d.ts +38 -0
  25. package/build/lib/harness-skill-targets.js +95 -0
  26. package/build/lib/project-context-layout.d.ts +15 -2
  27. package/build/lib/project-context-layout.js +27 -3
  28. package/build/lib/workflow-skill-installer.d.ts +2 -0
  29. package/build/lib/workflow-skill-installer.js +19 -5
  30. package/build/lib/workflow-skill-template.d.ts +3 -0
  31. package/build/lib/workflow-skill-template.js +12 -5
  32. package/build/lib/workflow-skill-version.d.ts +7 -1
  33. package/build/lib/workflow-skill-version.js +45 -2
  34. package/build/schemas/index.d.ts +1 -1
  35. package/build/schemas/output/project-tools.d.ts +16 -1
  36. package/build/schemas/output/project-tools.js +7 -1
  37. package/build/schemas/project-tools.d.ts +1 -1
  38. package/build/schemas/project-tools.js +1 -1
  39. package/build/tools/__tests__/add_feature.write.unit.test.js +3 -0
  40. package/build/tools/__tests__/init_project.write.unit.test.js +3 -0
  41. package/build/tools/__tests__/init_project_context.unit.test.js +28 -0
  42. package/build/tools/add_feature.js +1 -1
  43. package/build/tools/init_project.js +8 -6
  44. package/build/tools/init_project_context.js +52 -30
  45. package/package.json +84 -84
@@ -0,0 +1,166 @@
1
+ import * as fs from "node:fs";
2
+ import * as path from "node:path";
3
+ import { CANONICAL_SKILL_REL_PATH, detectHarnessContext, toLayoutHarnessManifest, } from "./harness-skill-targets.js";
4
+ import { getMcpProbeSkillVersion } from "./workflow-skill-version.js";
5
+ const RULES_POINTER_VERSION_KEY = "mcp-probe-kit-harness-adapter-version";
6
+ const CLAUDE_BLOCK_BEGIN = "<!-- mcp-probe:harness begin — auto-generated; do not edit -->";
7
+ const CLAUDE_BLOCK_END = "<!-- mcp-probe:harness end -->";
8
+ function generateRulesPointerContent(skillCanonical, version, agentsIndexPath) {
9
+ return `# mcp-probe-kit MCP
10
+
11
+ > ${RULES_POINTER_VERSION_KEY}: ${version}
12
+
13
+ 配置 **mcp-probe-kit** MCP 后,写代码 / 改文件前:
14
+
15
+ 1. 阅读项目根 \`${agentsIndexPath}\` 中 \`<!-- mcp-probe:context -->\` 块(MCP 触发规则)
16
+ 2. 需要完整工具表时阅读 \`${skillCanonical}\`
17
+
18
+ **不要**跳过 MCP 直接改业务代码。拿不准先调 \`workflow\`。
19
+ `;
20
+ }
21
+ function generateComateRulesContent(skillCanonical, version, agentsIndexPath) {
22
+ return `# mcp-probe-kit-mcp
23
+
24
+ > ${RULES_POINTER_VERSION_KEY}: ${version}
25
+
26
+ 配置 mcp-probe-kit MCP 后,写代码前阅读 ${agentsIndexPath} 的 mcp-probe 块或 ${skillCanonical}。拿不准先调 workflow。
27
+ `;
28
+ }
29
+ function generateClaudePointerBlock(skillCanonical, agentsPath) {
30
+ return `${CLAUDE_BLOCK_BEGIN}
31
+ ## MCP (mcp-probe-kit)
32
+
33
+ Before coding, read the \`mcp-probe:context\` block in \`${agentsPath}\` or Skill \`${skillCanonical}\`.
34
+ ${CLAUDE_BLOCK_END}`;
35
+ }
36
+ function stripClaudeHarnessBlock(content) {
37
+ const beginIdx = content.indexOf(CLAUDE_BLOCK_BEGIN);
38
+ if (beginIdx === -1) {
39
+ return content.trim();
40
+ }
41
+ const endIdx = content.indexOf(CLAUDE_BLOCK_END);
42
+ if (endIdx === -1) {
43
+ return content.trim();
44
+ }
45
+ const before = content.slice(0, beginIdx).trimEnd();
46
+ const after = content.slice(endIdx + CLAUDE_BLOCK_END.length).trimStart();
47
+ return [before, after].filter(Boolean).join("\n\n").trim();
48
+ }
49
+ function mergeClaudePointer(existing, block) {
50
+ const userBody = existing ? stripClaudeHarnessBlock(existing) : "";
51
+ if (!userBody) {
52
+ return `${block}\n`;
53
+ }
54
+ return `${block}\n\n${userBody}\n`;
55
+ }
56
+ function adapterNeedsUpdate(existing, nextContent, kind, agentsIndexPath) {
57
+ if (!existing?.trim()) {
58
+ return true;
59
+ }
60
+ if (kind === "skill-mirror") {
61
+ return existing !== nextContent;
62
+ }
63
+ const version = getMcpProbeSkillVersion();
64
+ if (!existing.includes(RULES_POINTER_VERSION_KEY) || !existing.includes(version)) {
65
+ return true;
66
+ }
67
+ if (kind === "claude-pointer") {
68
+ return (!existing.includes(CANONICAL_SKILL_REL_PATH) ||
69
+ Boolean(agentsIndexPath && !existing.includes(agentsIndexPath)));
70
+ }
71
+ if (kind === "rules-pointer" && agentsIndexPath && !existing.includes(agentsIndexPath)) {
72
+ return true;
73
+ }
74
+ return existing !== nextContent;
75
+ }
76
+ function resolveAdapterContent(adapter, skillContent, agentsIndexPath) {
77
+ const version = getMcpProbeSkillVersion();
78
+ switch (adapter.kind) {
79
+ case "skill-mirror":
80
+ return skillContent;
81
+ case "rules-pointer":
82
+ return adapter.relPath.endsWith(".mdr")
83
+ ? generateComateRulesContent(CANONICAL_SKILL_REL_PATH, version, agentsIndexPath)
84
+ : generateRulesPointerContent(CANONICAL_SKILL_REL_PATH, version, agentsIndexPath);
85
+ case "claude-pointer":
86
+ return mergeClaudePointer(null, generateClaudePointerBlock(CANONICAL_SKILL_REL_PATH, agentsIndexPath));
87
+ default:
88
+ return skillContent;
89
+ }
90
+ }
91
+ function writeAdapterFile(projectRoot, adapter, content, agentsIndexPath) {
92
+ const absolute = path.join(projectRoot, adapter.relPath);
93
+ const existing = fs.existsSync(absolute) ? fs.readFileSync(absolute, "utf8") : null;
94
+ if (adapter.kind === "claude-pointer" && existing) {
95
+ const block = generateClaudePointerBlock(CANONICAL_SKILL_REL_PATH, agentsIndexPath);
96
+ const merged = mergeClaudePointer(existing, block);
97
+ if (!adapterNeedsUpdate(existing, merged, adapter.kind, agentsIndexPath)) {
98
+ return {
99
+ id: adapter.id,
100
+ path: adapter.relPath,
101
+ kind: adapter.kind,
102
+ created: false,
103
+ updated: false,
104
+ skipped: true,
105
+ };
106
+ }
107
+ fs.mkdirSync(path.dirname(absolute), { recursive: true });
108
+ fs.writeFileSync(absolute, merged, "utf8");
109
+ return {
110
+ id: adapter.id,
111
+ path: adapter.relPath,
112
+ kind: adapter.kind,
113
+ created: false,
114
+ updated: true,
115
+ skipped: false,
116
+ };
117
+ }
118
+ if (!adapterNeedsUpdate(existing, content, adapter.kind, agentsIndexPath)) {
119
+ return {
120
+ id: adapter.id,
121
+ path: adapter.relPath,
122
+ kind: adapter.kind,
123
+ created: false,
124
+ updated: false,
125
+ skipped: true,
126
+ };
127
+ }
128
+ fs.mkdirSync(path.dirname(absolute), { recursive: true });
129
+ fs.writeFileSync(absolute, content, "utf8");
130
+ const hadContent = Boolean(existing?.trim());
131
+ return {
132
+ id: adapter.id,
133
+ path: adapter.relPath,
134
+ kind: adapter.kind,
135
+ created: !hadContent,
136
+ updated: hadContent,
137
+ skipped: false,
138
+ };
139
+ }
140
+ function collectInstalledAdapterManifest(projectRoot, detection) {
141
+ return detection.adaptersToWrite
142
+ .filter((adapter) => fs.existsSync(path.join(projectRoot, adapter.relPath)))
143
+ .map((adapter) => ({
144
+ id: adapter.id,
145
+ kind: adapter.kind,
146
+ path: adapter.relPath,
147
+ }));
148
+ }
149
+ /**
150
+ * Write optional harness adapters. AGENTS.md and canonical Skill are unchanged.
151
+ * Canonical Skill must already exist at `.agents/skills/mcp-probe-kit/SKILL.md`.
152
+ */
153
+ export function ensureHarnessAdapters(projectRoot, skillContent, agentsIndexPath = "AGENTS.md") {
154
+ const root = path.resolve(projectRoot);
155
+ const detection = detectHarnessContext(root);
156
+ const adapters = [];
157
+ for (const adapter of detection.adaptersToWrite) {
158
+ const content = resolveAdapterContent(adapter, skillContent, agentsIndexPath);
159
+ adapters.push(writeAdapterFile(root, adapter, content, agentsIndexPath));
160
+ }
161
+ return {
162
+ detection,
163
+ adapters,
164
+ layoutHarness: toLayoutHarnessManifest(detection, collectInstalledAdapterManifest(root, detection)),
165
+ };
166
+ }
@@ -0,0 +1,38 @@
1
+ /** Canonical Skill path — never changes per harness */
2
+ export declare const CANONICAL_SKILL_REL_PATH = ".agents/skills/mcp-probe-kit/SKILL.md";
3
+ export type HarnessId = "agents" | "cursor" | "claude" | "codex" | "opencode" | "trae" | "traecli" | "codebuddy" | "lingma" | "comate";
4
+ export type HarnessAdapterKind = "skill-mirror" | "rules-pointer" | "claude-pointer";
5
+ export interface HarnessAdapterTarget {
6
+ id: string;
7
+ harnessId: HarnessId;
8
+ relPath: string;
9
+ kind: HarnessAdapterKind;
10
+ /** 项目内已有该目录时才写适配层(零配置) */
11
+ markerDir: string;
12
+ }
13
+ export interface HarnessDetectionResult {
14
+ markerHarnesses: HarnessId[];
15
+ detected: HarnessId[];
16
+ skillCanonical: string;
17
+ adaptersToWrite: HarnessAdapterTarget[];
18
+ }
19
+ export declare const HARNESS_ADAPTER_TARGETS: HarnessAdapterTarget[];
20
+ /**
21
+ * 零配置 harness 检测:项目里已有工具目录(如 `.trae/`)则写对应薄适配。
22
+ * AGENTS.md 与 canonical Skill 路径始终不变。
23
+ */
24
+ export declare function detectHarnessContext(projectRoot: string): HarnessDetectionResult;
25
+ export interface LayoutHarnessManifest {
26
+ detected: HarnessId[];
27
+ skillCanonical: string;
28
+ adapters: Array<{
29
+ id: string;
30
+ kind: HarnessAdapterKind;
31
+ path: string;
32
+ }>;
33
+ }
34
+ export declare function toLayoutHarnessManifest(detection: HarnessDetectionResult, writtenAdapters: Array<{
35
+ id: string;
36
+ kind: HarnessAdapterKind;
37
+ path: string;
38
+ }>): LayoutHarnessManifest;
@@ -0,0 +1,95 @@
1
+ import * as fs from "node:fs";
2
+ import * as path from "node:path";
3
+ import { MCP_PROBE_SKILL_REL_PATH } from "./workflow-skill-template.js";
4
+ /** Canonical Skill path — never changes per harness */
5
+ export const CANONICAL_SKILL_REL_PATH = MCP_PROBE_SKILL_REL_PATH;
6
+ export const HARNESS_ADAPTER_TARGETS = [
7
+ {
8
+ id: "trae-skill",
9
+ harnessId: "trae",
10
+ relPath: ".trae/skills/mcp-probe-kit/SKILL.md",
11
+ kind: "skill-mirror",
12
+ markerDir: ".trae",
13
+ },
14
+ {
15
+ id: "traecli-skill",
16
+ harnessId: "traecli",
17
+ relPath: ".traecli/skills/mcp-probe-kit/SKILL.md",
18
+ kind: "skill-mirror",
19
+ markerDir: ".traecli",
20
+ },
21
+ {
22
+ id: "codebuddy-skill",
23
+ harnessId: "codebuddy",
24
+ relPath: ".codebuddy/skills/mcp-probe-kit/SKILL.md",
25
+ kind: "skill-mirror",
26
+ markerDir: ".codebuddy",
27
+ },
28
+ {
29
+ id: "lingma-rules",
30
+ harnessId: "lingma",
31
+ relPath: ".lingma/rules/mcp-probe-kit-mcp.md",
32
+ kind: "rules-pointer",
33
+ markerDir: ".lingma",
34
+ },
35
+ {
36
+ id: "comate-rules",
37
+ harnessId: "comate",
38
+ relPath: ".comate/rules/mcp-probe-kit-mcp.mdr",
39
+ kind: "rules-pointer",
40
+ markerDir: ".comate",
41
+ },
42
+ {
43
+ id: "claude-pointer",
44
+ harnessId: "claude",
45
+ relPath: "CLAUDE.md",
46
+ kind: "claude-pointer",
47
+ markerDir: ".claude",
48
+ },
49
+ ];
50
+ const MARKER_DIR_TO_HARNESS = {
51
+ ".trae": "trae",
52
+ ".traecli": "traecli",
53
+ ".codebuddy": "codebuddy",
54
+ ".lingma": "lingma",
55
+ ".comate": "comate",
56
+ ".cursor": "cursor",
57
+ ".claude": "claude",
58
+ ".codex": "codex",
59
+ ".opencode": "opencode",
60
+ };
61
+ function detectMarkerHarnesses(projectRoot) {
62
+ const found = [];
63
+ for (const [markerDir, harnessId] of Object.entries(MARKER_DIR_TO_HARNESS)) {
64
+ if (fs.existsSync(path.join(projectRoot, markerDir))) {
65
+ found.push(harnessId);
66
+ }
67
+ }
68
+ return found;
69
+ }
70
+ function shouldWriteAdapter(projectRoot, adapter) {
71
+ return fs.existsSync(path.join(projectRoot, adapter.markerDir));
72
+ }
73
+ /**
74
+ * 零配置 harness 检测:项目里已有工具目录(如 `.trae/`)则写对应薄适配。
75
+ * AGENTS.md 与 canonical Skill 路径始终不变。
76
+ */
77
+ export function detectHarnessContext(projectRoot) {
78
+ const root = path.resolve(projectRoot);
79
+ const markerHarnesses = detectMarkerHarnesses(root);
80
+ const detected = markerHarnesses.length > 0 ? markerHarnesses : ["agents"];
81
+ const adaptersToWrite = HARNESS_ADAPTER_TARGETS.filter((adapter) => shouldWriteAdapter(root, adapter));
82
+ return {
83
+ markerHarnesses,
84
+ detected,
85
+ skillCanonical: CANONICAL_SKILL_REL_PATH,
86
+ adaptersToWrite,
87
+ };
88
+ }
89
+ export function toLayoutHarnessManifest(detection, writtenAdapters) {
90
+ return {
91
+ detected: detection.detected,
92
+ skillCanonical: detection.skillCanonical,
93
+ adapters: writtenAdapters,
94
+ };
95
+ }
@@ -32,6 +32,15 @@ export interface ProjectContextLayoutArgs {
32
32
  }
33
33
  /** Primary env key recorded in layout.json (optional local override via projectRoot) */
34
34
  export declare const LAYOUT_PROJECT_ROOT_ENV = "MCP_PROJECT_ROOT";
35
+ export interface LayoutManifestHarnessV1 {
36
+ detected: string[];
37
+ skillCanonical: string;
38
+ adapters: Array<{
39
+ id: string;
40
+ kind: string;
41
+ path: string;
42
+ }>;
43
+ }
35
44
  export interface LayoutManifestV1 {
36
45
  version: 1;
37
46
  /** Ignored if present in old files — project root is always inferred from manifest path */
@@ -45,6 +54,8 @@ export interface LayoutManifestV1 {
45
54
  indexStyle: "agents" | "legacy";
46
55
  generatedBy: string;
47
56
  generatedAt: string;
57
+ /** Harness detection snapshot (AGENTS.md + canonical Skill stay harness-agnostic) */
58
+ harness?: LayoutManifestHarnessV1;
48
59
  }
49
60
  export declare function toPosixPath(value: string): string;
50
61
  export declare function relativeLink(fromRel: string, toRel: string): string;
@@ -66,8 +77,10 @@ export declare function manifestPathRelativeToProject(projectRoot: string, absol
66
77
  export declare function layoutAbsPath(layout: ProjectContextLayout, relativePath: string): string;
67
78
  type ProjectContextLayoutCore = Omit<ProjectContextLayout, "projectRoot" | "projectRootPosix">;
68
79
  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;
80
+ export declare function buildLayoutManifest(layout: ProjectContextLayout, harness?: LayoutManifestHarnessV1): LayoutManifestV1;
81
+ export declare function writeLayoutManifest(projectRoot: string, layout: ProjectContextLayout, harness?: LayoutManifestHarnessV1): string;
82
+ /** Patch harness section on an existing layout.json (e.g. after bootstrap adapters). */
83
+ export declare function patchLayoutManifestHarness(projectRoot: string, harness: LayoutManifestHarnessV1): string | null;
71
84
  export declare function layoutFromManifest(manifest: LayoutManifestV1, fallbackProjectRoot: string, manifestFilePath?: string): ProjectContextLayout;
72
85
  export declare function resolveProjectContextLayout(projectRoot: string, args?: ProjectContextLayoutArgs): ProjectContextLayout;
73
86
  export declare function countCjkChars(text: string): number;
@@ -196,7 +196,7 @@ export function attachProjectRoot(layout, projectRoot) {
196
196
  projectRootPosix: projectRootToManifestValue(resolved),
197
197
  };
198
198
  }
199
- export function buildLayoutManifest(layout) {
199
+ export function buildLayoutManifest(layout, harness) {
200
200
  return {
201
201
  version: 1,
202
202
  projectRootEnv: LAYOUT_PROJECT_ROOT_ENV,
@@ -207,17 +207,41 @@ export function buildLayoutManifest(layout) {
207
207
  indexStyle: layout.indexStyle,
208
208
  generatedBy: "init_project_context",
209
209
  generatedAt: new Date().toISOString(),
210
+ ...(harness ? { harness } : {}),
210
211
  };
211
212
  }
212
- export function writeLayoutManifest(projectRoot, layout) {
213
+ export function writeLayoutManifest(projectRoot, layout, harness) {
213
214
  const resolvedRoot = path.resolve(projectRoot);
214
215
  const manifestRel = layoutManifestRel(layout.contextRoot);
215
- const manifest = buildLayoutManifest(attachProjectRoot(layout, resolvedRoot));
216
+ const manifest = buildLayoutManifest(attachProjectRoot(layout, resolvedRoot), harness);
216
217
  const absoluteManifest = path.join(resolvedRoot, ...manifestRel.split("/"));
217
218
  fs.mkdirSync(path.dirname(absoluteManifest), { recursive: true });
218
219
  fs.writeFileSync(absoluteManifest, `${JSON.stringify(manifest, null, 2)}\n`, "utf8");
219
220
  return manifestRel;
220
221
  }
222
+ function layoutHarnessEquals(left, right) {
223
+ return JSON.stringify(left ?? null) === JSON.stringify(right);
224
+ }
225
+ /** Patch harness section on an existing layout.json (e.g. after bootstrap adapters). */
226
+ export function patchLayoutManifestHarness(projectRoot, harness) {
227
+ const resolvedRoot = path.resolve(projectRoot);
228
+ const existing = readLayoutManifest(resolvedRoot);
229
+ if (!existing) {
230
+ return null;
231
+ }
232
+ if (layoutHarnessEquals(existing.harness, harness)) {
233
+ return null;
234
+ }
235
+ const layout = layoutFromManifest(existing, resolvedRoot);
236
+ const manifestRel = layout.manifestPath;
237
+ const absoluteManifest = path.join(resolvedRoot, ...manifestRel.split("/"));
238
+ const updated = {
239
+ ...existing,
240
+ harness,
241
+ };
242
+ fs.writeFileSync(absoluteManifest, `${JSON.stringify(updated, null, 2)}\n`, "utf8");
243
+ return manifestRel;
244
+ }
221
245
  export function layoutFromManifest(manifest, fallbackProjectRoot, manifestFilePath) {
222
246
  const contextRoot = normalizeRelativePath(manifest.contextRoot);
223
247
  const projectRoot = resolveManifestProjectRoot(manifest, fallbackProjectRoot, manifestFilePath);
@@ -1,3 +1,4 @@
1
+ import { type HarnessAdapterEnsureResult } from "./harness-adapters.js";
1
2
  export interface SkillEnsureResult {
2
3
  skillPath: string;
3
4
  skillRelPath: string;
@@ -17,6 +18,7 @@ export interface McpProbeKitBootstrapResult {
17
18
  projectRoot: string;
18
19
  skill: SkillEnsureResult;
19
20
  agentsMd: AgentsMdEnsureResult;
21
+ harness?: HarnessAdapterEnsureResult;
20
22
  /** 工作区可能解析失败(写到了 mcp-probe-kit 安装目录) */
21
23
  workspaceWarning?: string;
22
24
  }
@@ -1,10 +1,12 @@
1
1
  import * as fs from "node:fs";
2
2
  import * as path from "node:path";
3
+ import { agentsSkillReferenceSatisfied, resolveAgentsSkillRefMode, } from "./agents-skill-ref.js";
3
4
  import { generateAgentsMdInner } from "./agents-md-template.js";
4
5
  import { mergeAgentsMdBlock } from "./merge-agents-md.js";
5
- import { detectDocumentLocale, resolveProjectContextLayout, toPosixPath, } from "./project-context-layout.js";
6
6
  import { generateWorkflowSkillContent, LEGACY_WORKFLOW_SKILL_REL_PATH, MCP_PROBE_SKILL_REL_PATH, } from "./workflow-skill-template.js";
7
- import { agentsContextNeedsUpgrade, getMcpProbeSkillVersion, parseSkillVersionMarker, skillContentNeedsUpgrade, } from "./workflow-skill-version.js";
7
+ import { agentsContextNeedsUpgrade, getMcpProbeSkillVersion, parseSkillInstalledVersion, skillContentNeedsUpgrade, } from "./workflow-skill-version.js";
8
+ import { ensureHarnessAdapters } from "./harness-adapters.js";
9
+ import { detectDocumentLocale, patchLayoutManifestHarness, resolveProjectContextLayout, toPosixPath, } from "./project-context-layout.js";
8
10
  import { isLikelyProjectNamedRelativePath, getMcpPackageInstallRoot, resolveWorkspaceRoot, } from "./workspace-root.js";
9
11
  function buildWorkspaceWarning(projectRoot) {
10
12
  const normalizedRoot = path.resolve(projectRoot);
@@ -42,6 +44,7 @@ function buildAgentsMdInner(projectRoot, existingAgentsContent) {
42
44
  const layout = resolveProjectContextLayout(projectRoot);
43
45
  const locale = detectDocumentLocale(projectRoot, existingAgentsContent);
44
46
  const graphReady = fs.existsSync(path.join(projectRoot, layout.latestMarkdownPath));
47
+ const contextReady = fs.existsSync(path.join(projectRoot, layout.legacyIndexPath));
45
48
  return generateAgentsMdInner({
46
49
  layout,
47
50
  locale,
@@ -53,6 +56,7 @@ function buildAgentsMdInner(projectRoot, existingAgentsContent) {
53
56
  docs: [],
54
57
  projectRootPosix: toPosixPath(projectRoot),
55
58
  graphReady,
59
+ contextReady,
56
60
  });
57
61
  }
58
62
  function agentsMdNeedsUpdate(content, skillRelPath, targetVersion) {
@@ -68,6 +72,9 @@ function agentsMdNeedsUpdate(content, skillRelPath, targetVersion) {
68
72
  if (!content.includes(skillRelPath)) {
69
73
  return true;
70
74
  }
75
+ if (!agentsSkillReferenceSatisfied(content, resolveAgentsSkillRefMode())) {
76
+ return true;
77
+ }
71
78
  if (agentsContextNeedsUpgrade(content, targetVersion)) {
72
79
  return true;
73
80
  }
@@ -85,7 +92,7 @@ export function ensureMcpProbeSkill(projectRoot) {
85
92
  const skillPath = path.join(root, MCP_PROBE_SKILL_REL_PATH);
86
93
  const targetVersion = getMcpProbeSkillVersion();
87
94
  const existing = fs.existsSync(skillPath) ? fs.readFileSync(skillPath, "utf8") : null;
88
- const previousVersion = existing ? parseSkillVersionMarker(existing) : null;
95
+ const previousVersion = existing ? parseSkillInstalledVersion(existing) : null;
89
96
  if (!skillContentNeedsUpgrade(existing, targetVersion)) {
90
97
  return {
91
98
  skillPath,
@@ -144,10 +151,17 @@ export function ensureAgentsMdSkillReference(projectRoot) {
144
151
  export function ensureMcpProbeKitBootstrap(projectRoot) {
145
152
  const root = path.resolve(projectRoot);
146
153
  const workspaceWarning = buildWorkspaceWarning(root);
154
+ const skill = ensureMcpProbeSkill(root);
155
+ const agentsMd = ensureAgentsMdSkillReference(root);
156
+ const skillContent = fs.readFileSync(skill.skillPath, "utf8");
157
+ const layout = resolveProjectContextLayout(root);
158
+ const harness = ensureHarnessAdapters(root, skillContent, layout.indexPath);
159
+ patchLayoutManifestHarness(root, harness.layoutHarness);
147
160
  return {
148
161
  projectRoot: root,
149
- skill: ensureMcpProbeSkill(root),
150
- agentsMd: ensureAgentsMdSkillReference(root),
162
+ skill,
163
+ agentsMd,
164
+ harness,
151
165
  workspaceWarning,
152
166
  };
153
167
  }
@@ -6,4 +6,7 @@ export declare const MCP_PROBE_SKILL_REL_PATH = ".agents/skills/mcp-probe-kit/SK
6
6
  /** @deprecated 旧路径,仅用于检测并升级 AGENTS.md 引用 */
7
7
  export declare const LEGACY_WORKFLOW_SKILL_REL_PATH = ".agents/skills/workflow/SKILL.md";
8
8
  export declare function generateWorkflowSkillBody(skillVersion?: string): string;
9
+ export declare const MCP_PROBE_SKILL_NAME = "mcp-probe-kit";
10
+ export declare const MCP_PROBE_SKILL_DESCRIPTION = "\u5C06\u7528\u6237\u610F\u56FE\u8DEF\u7531\u5230 mcp-probe-kit MCP \u5DE5\u5177\uFF08start_feature\u3001start_bugfix\u3001code_insight\u3001workflow\u3001gencommit \u7B49\uFF09\u3002\u5728\u5DF2\u914D\u7F6E MCP \u4E14\u51C6\u5907\u5199\u4EE3\u7801\u524D\u8BFB\u53D6\uFF1B\u4EC5\u8BF4\u660E\u8C03\u54EA\u4E2A MCP\uFF0C\u4E0D\u662F\u9879\u76EE\u7814\u53D1\u6D41\u7A0B\u672C\u8EAB\u3002Routes intent to mcp-probe-kit MCP tools; read before coding when MCP is configured.";
11
+ export declare function formatSkillFrontmatter(skillVersion?: string): string;
9
12
  export declare function generateWorkflowSkillContent(skillVersion?: string): string;
@@ -4,7 +4,7 @@
4
4
  */
5
5
  import { VERSION } from "../version.js";
6
6
  import { MCP_INTENT_QUICK_LOOKUP, MCP_SKILL_AVOID_RULES, MCP_SKILL_COMMON_FLOWS, MCP_TOOL_SKILL_GROUPS, } from "./mcp-tool-skill-registry.js";
7
- import { formatSkillVersionMarker } from "./workflow-skill-version.js";
7
+ import { SKILL_VERSION_FRONTMATTER_KEY } from "./workflow-skill-version.js";
8
8
  export const MCP_PROBE_SKILL_REL_PATH = ".agents/skills/mcp-probe-kit/SKILL.md";
9
9
  /** @deprecated 旧路径,仅用于检测并升级 AGENTS.md 引用 */
10
10
  export const LEGACY_WORKFLOW_SKILL_REL_PATH = ".agents/skills/workflow/SKILL.md";
@@ -74,12 +74,19 @@ ${renderAvoidRules()}
74
74
  *mcp-probe-kit 按版本自动同步(当前 \`${skillVersion}\`)。路径:\`${MCP_PROBE_SKILL_REL_PATH}\`*
75
75
  `;
76
76
  }
77
+ export const MCP_PROBE_SKILL_NAME = "mcp-probe-kit";
78
+ export const MCP_PROBE_SKILL_DESCRIPTION = `将用户意图路由到 mcp-probe-kit MCP 工具(start_feature、start_bugfix、code_insight、workflow、gencommit 等)。在已配置 MCP 且准备写代码前读取;仅说明调哪个 MCP,不是项目研发流程本身。Routes intent to mcp-probe-kit MCP tools; read before coding when MCP is configured.`;
79
+ export function formatSkillFrontmatter(skillVersion = VERSION) {
80
+ return `---
81
+ name: ${MCP_PROBE_SKILL_NAME}
82
+ description: >-
83
+ ${MCP_PROBE_SKILL_DESCRIPTION}
84
+ ${SKILL_VERSION_FRONTMATTER_KEY}: "${skillVersion}"
85
+ ---`;
86
+ }
77
87
  export function generateWorkflowSkillContent(skillVersion = VERSION) {
78
- const versionMarker = formatSkillVersionMarker(skillVersion);
79
- return `${versionMarker}
88
+ return `${formatSkillFrontmatter(skillVersion)}
80
89
 
81
90
  ${generateWorkflowSkillBody(skillVersion)}
82
-
83
- ${versionMarker}
84
91
  `;
85
92
  }
@@ -1,4 +1,6 @@
1
- /** Skill 文件首行版本标记:`<!-- mcp-probe-kit-skill-version: 3.5.0 -->` */
1
+ /** Skill frontmatter 版本字段 */
2
+ export declare const SKILL_VERSION_FRONTMATTER_KEY = "mcp-probe-kit-version";
3
+ /** @deprecated 旧版 HTML 版本标记,仍用于解析历史文件 */
2
4
  export declare const SKILL_VERSION_MARKER = "mcp-probe-kit-skill-version";
3
5
  /** AGENTS.md mcp-probe 块内版本标记 */
4
6
  export declare const AGENTS_CONTEXT_VERSION_MARKER = "mcp-probe:context-version";
@@ -6,6 +8,10 @@ export declare function getMcpProbeSkillVersion(): string;
6
8
  export declare function formatSkillVersionMarker(version?: string): string;
7
9
  export declare function formatAgentsContextVersionMarker(version?: string): string;
8
10
  export declare function parseSkillVersionMarker(content: string): string | null;
11
+ /** 从 Skill 正文解析已安装版本(frontmatter 优先,HTML 注释兜底) */
12
+ export declare function parseSkillInstalledVersion(content: string): string | null;
13
+ /** 是否缺少 Agent Skill 发现所需的 YAML frontmatter */
14
+ export declare function skillFrontmatterNeedsUpgrade(content: string): boolean;
9
15
  export declare function parseAgentsContextVersion(content: string): string | null;
10
16
  /** 比较 semver(仅主版本号段,忽略 prerelease 后缀) */
11
17
  export declare function compareSemver(a: string, b: string): number;
@@ -1,5 +1,7 @@
1
1
  import { VERSION } from "../version.js";
2
- /** Skill 文件首行版本标记:`<!-- mcp-probe-kit-skill-version: 3.5.0 -->` */
2
+ /** Skill frontmatter 版本字段 */
3
+ export const SKILL_VERSION_FRONTMATTER_KEY = "mcp-probe-kit-version";
4
+ /** @deprecated 旧版 HTML 版本标记,仍用于解析历史文件 */
3
5
  export const SKILL_VERSION_MARKER = "mcp-probe-kit-skill-version";
4
6
  /** AGENTS.md mcp-probe 块内版本标记 */
5
7
  export const AGENTS_CONTEXT_VERSION_MARKER = "mcp-probe:context-version";
@@ -17,6 +19,44 @@ export function parseSkillVersionMarker(content) {
17
19
  const match = content.match(pattern);
18
20
  return match?.[1]?.trim() ?? null;
19
21
  }
22
+ function parseSkillVersionFromFrontmatter(content) {
23
+ if (!content.trimStart().startsWith("---")) {
24
+ return null;
25
+ }
26
+ const end = content.indexOf("\n---", 3);
27
+ if (end === -1) {
28
+ return null;
29
+ }
30
+ const header = content.slice(0, end + 4);
31
+ const pattern = new RegExp(`^${SKILL_VERSION_FRONTMATTER_KEY}:\\s*["']?([^"'\\n]+)["']?\\s*$`, "m");
32
+ const match = header.match(pattern);
33
+ return match?.[1]?.trim().replace(/^["']|["']$/g, "") ?? null;
34
+ }
35
+ /** 从 Skill 正文解析已安装版本(frontmatter 优先,HTML 注释兜底) */
36
+ export function parseSkillInstalledVersion(content) {
37
+ return parseSkillVersionFromFrontmatter(content) ?? parseSkillVersionMarker(content);
38
+ }
39
+ /** 是否缺少 Agent Skill 发现所需的 YAML frontmatter */
40
+ export function skillFrontmatterNeedsUpgrade(content) {
41
+ if (!content.trimStart().startsWith("---")) {
42
+ return true;
43
+ }
44
+ const end = content.indexOf("\n---", 3);
45
+ if (end === -1) {
46
+ return true;
47
+ }
48
+ const header = content.slice(0, end + 4);
49
+ if (!/^name:\s*mcp-probe-kit\s*$/m.test(header)) {
50
+ return true;
51
+ }
52
+ if (!/^description:\s*>?-?\s*\S/m.test(header)) {
53
+ return true;
54
+ }
55
+ if (!new RegExp(`^${SKILL_VERSION_FRONTMATTER_KEY}:\\s*`, "m").test(header)) {
56
+ return true;
57
+ }
58
+ return false;
59
+ }
20
60
  export function parseAgentsContextVersion(content) {
21
61
  const pattern = new RegExp(`<!--\\s*${AGENTS_CONTEXT_VERSION_MARKER}:\\s*([^\\s>]+)\\s*-->`, "i");
22
62
  const match = content.match(pattern);
@@ -49,7 +89,10 @@ export function skillContentNeedsUpgrade(existing, targetVersion = VERSION) {
49
89
  if (!existing?.trim()) {
50
90
  return true;
51
91
  }
52
- const installed = parseSkillVersionMarker(existing);
92
+ if (skillFrontmatterNeedsUpgrade(existing)) {
93
+ return true;
94
+ }
95
+ const installed = parseSkillInstalledVersion(existing);
53
96
  if (!installed) {
54
97
  return true;
55
98
  }
@@ -218,7 +218,7 @@ export declare const allToolSchemas: ({
218
218
  };
219
219
  } | {
220
220
  readonly name: "init_project_context";
221
- readonly description: "生成/更新项目上下文:默认写入 AGENTS.md(含 MCP GitNexus 触发规则)及 docs/project-context/。新功能请先 start_feature,修 bug 请先 start_bugfix。完成后 Agent 应阅读 AGENTS.md。";
221
+ readonly description: "生成/更新项目上下文写作计划(delegated):MCP 写入 AGENTS.md 与 layout.json;project-context 分类文档与 graph-insights 由 Agent 按返回的 plan 落盘。新功能请先 start_feature,修 bug 请先 start_bugfix。";
222
222
  readonly inputSchema: {
223
223
  readonly type: "object";
224
224
  readonly properties: {
@@ -216,9 +216,21 @@ export declare const ProjectContextSchema: {
216
216
  readonly purpose: {
217
217
  readonly type: "string";
218
218
  };
219
+ readonly exists: {
220
+ readonly type: "boolean";
221
+ readonly description: "文件是否已存在于磁盘";
222
+ };
223
+ readonly written: {
224
+ readonly type: "boolean";
225
+ readonly description: "内容是否已落盘(MCP 已写或 Agent 无需再写)";
226
+ };
227
+ readonly agent_action_required: {
228
+ readonly type: "boolean";
229
+ readonly description: "是否仍需 Agent 按 plan 手动写入";
230
+ };
219
231
  };
220
232
  };
221
- readonly description: "文档索引(含已写入与待生成)";
233
+ readonly description: "文档索引(含 exists / written / agent_action_required 状态)";
222
234
  };
223
235
  readonly writtenFiles: {
224
236
  readonly type: "array";
@@ -602,6 +614,9 @@ export interface ProjectContext {
602
614
  documentation?: Array<{
603
615
  path?: string;
604
616
  purpose?: string;
617
+ exists?: boolean;
618
+ written?: boolean;
619
+ agent_action_required?: boolean;
605
620
  }>;
606
621
  nextSteps?: string[];
607
622
  metadata?: Record<string, any>;
@@ -120,9 +120,15 @@ export const ProjectContextSchema = {
120
120
  properties: {
121
121
  path: { type: 'string' },
122
122
  purpose: { type: 'string' },
123
+ exists: { type: 'boolean', description: '文件是否已存在于磁盘' },
124
+ written: { type: 'boolean', description: '内容是否已落盘(MCP 已写或 Agent 无需再写)' },
125
+ agent_action_required: {
126
+ type: 'boolean',
127
+ description: '是否仍需 Agent 按 plan 手动写入',
128
+ },
123
129
  },
124
130
  },
125
- description: '文档索引(含已写入与待生成)',
131
+ description: '文档索引(含 exists / written / agent_action_required 状态)',
126
132
  },
127
133
  writtenFiles: {
128
134
  type: 'array',