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
package/README.md CHANGED
@@ -421,7 +421,9 @@ No installation needed, use the latest version directly.
421
421
  }
422
422
  ```
423
423
 
424
- > **Skill 自动安装**:任意 MCP 工具调用会在用户项目写入 `.agents/skills/mcp-probe-kit/SKILL.md`。工作区根目录**自动识别**(Cursor 注入 `WORKSPACE_FOLDER_PATHS`;OpenCode 等项目级 `opencode.json` 会设置进程 cwd),**无需**在每台客户端配置 `MCP_PROJECT_ROOT`。仅全局 MCP 且无法识别工作区时,可选手动设置 `MCP_PROJECT_ROOT` 或在工具参数传 `project_root`。需 **v3.6.3+**。
424
+ > **Skill & AGENTS auto-bootstrap (v3.6.3+)**: Every MCP tool call writes `.agents/skills/mcp-probe-kit/SKILL.md` and merges the `mcp-probe:context` block into `AGENTS.md`. Workspace root is **auto-detected** (Cursor injects `WORKSPACE_FOLDER_PATHS`; OpenCode project `opencode.json` sets cwd). No per-client `MCP_PROJECT_ROOT` unless global MCP cannot resolve the workspace — then set `MCP_PROJECT_ROOT` or pass `project_root` in tool args.
425
+
426
+ > **Multi-harness adapters (v3.6.8+)**: `AGENTS.md` and the canonical Skill stay the **single rule source**. If the project already has `.trae/`, `.lingma/`, `.comate/`, `.codebuddy/`, or `.claude/`, matching thin adapters (skill mirror or rules pointer) are written automatically — **no env vars**.
425
427
 
426
428
  #### Claude Desktop Configuration
427
429
 
package/build/index.js CHANGED
@@ -389,6 +389,7 @@ function withBootstrapMeta(result, bootstrap) {
389
389
  projectRoot: bootstrap.projectRoot,
390
390
  skill: bootstrap.skill,
391
391
  agentsMd: bootstrap.agentsMd,
392
+ harness: bootstrap.harness ?? null,
392
393
  workspaceWarning: bootstrap.workspaceWarning ?? null,
393
394
  },
394
395
  },
@@ -21,6 +21,8 @@ describe('generateAgentsMdInner', () => {
21
21
  expect(md).toContain('start_bugfix');
22
22
  expect(md).toContain('memorize_asset');
23
23
  expect(md).toContain('mcp-probe-kit/SKILL.md');
24
+ expect(md).toContain('@.agents/skills/mcp-probe-kit/SKILL.md');
25
+ expect(md).toContain('待 Agent 落盘');
24
26
  expect(md).toContain('bugfix');
25
27
  expect(md).toContain('search_memory');
26
28
  expect(md).toContain('自动注入');
@@ -0,0 +1,32 @@
1
+ import { afterEach, describe, expect, test } from "vitest";
2
+ import { agentsSkillReferenceSatisfied, formatAgentsSkillReference, resolveAgentsSkillRefMode, } from "../agents-skill-ref.js";
3
+ import { MCP_PROBE_SKILL_REL_PATH } from "../workflow-skill-template.js";
4
+ const prev = process.env.MCP_AGENTS_SKILL_REF;
5
+ afterEach(() => {
6
+ if (prev === undefined) {
7
+ delete process.env.MCP_AGENTS_SKILL_REF;
8
+ }
9
+ else {
10
+ process.env.MCP_AGENTS_SKILL_REF = prev;
11
+ }
12
+ });
13
+ describe("agents-skill-ref", () => {
14
+ test("默认 both 模式包含 @ 与链接", () => {
15
+ delete process.env.MCP_AGENTS_SKILL_REF;
16
+ const text = formatAgentsSkillReference(".agents/skills/mcp-probe-kit/SKILL.md");
17
+ expect(text).toContain(`@${MCP_PROBE_SKILL_REL_PATH}`);
18
+ expect(text).toContain("[MCP 调用时机]");
19
+ });
20
+ test("at 模式仅 @ 引用", () => {
21
+ process.env.MCP_AGENTS_SKILL_REF = "at";
22
+ expect(resolveAgentsSkillRefMode()).toBe("at");
23
+ const text = formatAgentsSkillReference("skill.md");
24
+ expect(text).toContain("@");
25
+ expect(text).not.toContain("[MCP 调用时机]");
26
+ });
27
+ test("agentsSkillReferenceSatisfied 校验 both", () => {
28
+ const ok = `before @${MCP_PROBE_SKILL_REL_PATH} and [link](${MCP_PROBE_SKILL_REL_PATH})`;
29
+ expect(agentsSkillReferenceSatisfied(ok, "both")).toBe(true);
30
+ expect(agentsSkillReferenceSatisfied(`only ${MCP_PROBE_SKILL_REL_PATH}`, "both")).toBe(false);
31
+ });
32
+ });
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,62 @@
1
+ import * as fs from "node:fs";
2
+ import * as os from "node:os";
3
+ import * as path from "node:path";
4
+ import { afterEach, describe, expect, test } from "vitest";
5
+ import { buildFileStatusEntries } from "../file-delivery.js";
6
+ const tempDirs = [];
7
+ afterEach(() => {
8
+ while (tempDirs.length > 0) {
9
+ const dir = tempDirs.pop();
10
+ if (dir) {
11
+ fs.rmSync(dir, { recursive: true, force: true });
12
+ }
13
+ }
14
+ });
15
+ describe("buildFileStatusEntries", () => {
16
+ test("marks MCP-created files as written", () => {
17
+ const projectRoot = fs.mkdtempSync(path.join(os.tmpdir(), "file-delivery-"));
18
+ tempDirs.push(projectRoot);
19
+ const result = buildFileStatusEntries(projectRoot, [{ path: "docs/guide.md", purpose: "User guide" }], [{ path: "docs/guide.md", action: "created" }], []);
20
+ expect(result).toEqual([
21
+ {
22
+ path: "docs/guide.md",
23
+ purpose: "User guide",
24
+ exists: false,
25
+ written: true,
26
+ agent_action_required: false,
27
+ },
28
+ ]);
29
+ });
30
+ test("treats pre-existing on-disk files as written when not pending", () => {
31
+ const projectRoot = fs.mkdtempSync(path.join(os.tmpdir(), "file-delivery-"));
32
+ tempDirs.push(projectRoot);
33
+ const relPath = "docs/existing.md";
34
+ fs.mkdirSync(path.join(projectRoot, "docs"), { recursive: true });
35
+ fs.writeFileSync(path.join(projectRoot, relPath), "# existing", "utf8");
36
+ const result = buildFileStatusEntries(projectRoot, [{ path: relPath }], [], []);
37
+ expect(result).toEqual([
38
+ {
39
+ path: relPath,
40
+ exists: true,
41
+ written: true,
42
+ agent_action_required: false,
43
+ },
44
+ ]);
45
+ });
46
+ test("pending files require agent action and are not written", () => {
47
+ const projectRoot = fs.mkdtempSync(path.join(os.tmpdir(), "file-delivery-"));
48
+ tempDirs.push(projectRoot);
49
+ const relPath = "docs/pending.md";
50
+ fs.mkdirSync(path.join(projectRoot, "docs"), { recursive: true });
51
+ fs.writeFileSync(path.join(projectRoot, relPath), "stub", "utf8");
52
+ const result = buildFileStatusEntries(projectRoot, [{ path: relPath }], [{ path: relPath, action: "skipped" }], [{ path: relPath, reason: "Agent must fill template" }]);
53
+ expect(result).toEqual([
54
+ {
55
+ path: relPath,
56
+ exists: true,
57
+ written: false,
58
+ agent_action_required: true,
59
+ },
60
+ ]);
61
+ });
62
+ });
@@ -0,0 +1,68 @@
1
+ import * as fs from "node:fs";
2
+ import * as os from "node:os";
3
+ import * as path from "node:path";
4
+ import { afterEach, describe, expect, test } from "vitest";
5
+ import { ensureHarnessAdapters } from "../harness-adapters.js";
6
+ import { patchLayoutManifestHarness, readLayoutManifest, resolveProjectContextLayout, writeLayoutManifest, } from "../project-context-layout.js";
7
+ import { generateWorkflowSkillContent } from "../workflow-skill-template.js";
8
+ import { VERSION } from "../../version.js";
9
+ const tempDirs = [];
10
+ afterEach(() => {
11
+ while (tempDirs.length > 0) {
12
+ const dir = tempDirs.pop();
13
+ if (dir) {
14
+ fs.rmSync(dir, { recursive: true, force: true });
15
+ }
16
+ }
17
+ });
18
+ describe("harness-adapters", () => {
19
+ test("规则指针使用自定义 indexPath", () => {
20
+ const root = fs.mkdtempSync(path.join(os.tmpdir(), "harness-adapter-"));
21
+ tempDirs.push(root);
22
+ fs.mkdirSync(path.join(root, ".lingma"), { recursive: true });
23
+ const customIndex = "docs/project-context.md";
24
+ ensureHarnessAdapters(root, generateWorkflowSkillContent(VERSION), customIndex);
25
+ const text = fs.readFileSync(path.join(root, ".lingma/rules/mcp-probe-kit-mcp.md"), "utf8");
26
+ expect(text).toContain(customIndex);
27
+ expect(text).not.toContain("阅读项目根 `AGENTS.md`");
28
+ });
29
+ test("第二次 bootstrap 仍记录已存在的 adapters", () => {
30
+ const root = fs.mkdtempSync(path.join(os.tmpdir(), "harness-adapter-"));
31
+ tempDirs.push(root);
32
+ fs.mkdirSync(path.join(root, ".trae"), { recursive: true });
33
+ const skill = generateWorkflowSkillContent(VERSION);
34
+ const first = ensureHarnessAdapters(root, skill);
35
+ expect(first.layoutHarness.adapters).toHaveLength(1);
36
+ const second = ensureHarnessAdapters(root, skill);
37
+ expect(second.adapters[0]?.skipped).toBe(true);
38
+ expect(second.layoutHarness.adapters).toEqual(first.layoutHarness.adapters);
39
+ });
40
+ test("patchLayoutManifestHarness 保留 generatedAt 且无变化时不写盘", () => {
41
+ const root = fs.mkdtempSync(path.join(os.tmpdir(), "harness-adapter-"));
42
+ tempDirs.push(root);
43
+ const layout = resolveProjectContextLayout(root, {});
44
+ writeLayoutManifest(root, layout, {
45
+ detected: ["agents"],
46
+ skillCanonical: ".agents/skills/mcp-probe-kit/SKILL.md",
47
+ adapters: [],
48
+ });
49
+ const manifestPath = path.join(root, "docs", ".mcp-probe", "layout.json");
50
+ const before = JSON.parse(fs.readFileSync(manifestPath, "utf8"));
51
+ const generatedAt = before.generatedAt;
52
+ const generatedBy = before.generatedBy;
53
+ const noop = patchLayoutManifestHarness(root, before.harness);
54
+ expect(noop).toBeNull();
55
+ const afterNoop = JSON.parse(fs.readFileSync(manifestPath, "utf8"));
56
+ expect(afterNoop.generatedAt).toBe(generatedAt);
57
+ fs.mkdirSync(path.join(root, ".trae"), { recursive: true });
58
+ const harnessResult = ensureHarnessAdapters(root, generateWorkflowSkillContent(VERSION));
59
+ const patched = patchLayoutManifestHarness(root, harnessResult.layoutHarness);
60
+ expect(patched).not.toBeNull();
61
+ const afterPatch = readLayoutManifest(root);
62
+ expect(afterPatch?.generatedAt).toBe(generatedAt);
63
+ expect(afterPatch?.generatedBy).toBe(generatedBy);
64
+ expect(afterPatch?.harness?.adapters).toHaveLength(1);
65
+ const patchedAgain = patchLayoutManifestHarness(root, harnessResult.layoutHarness);
66
+ expect(patchedAgain).toBeNull();
67
+ });
68
+ });
@@ -0,0 +1,59 @@
1
+ import * as fs from "node:fs";
2
+ import * as os from "node:os";
3
+ import * as path from "node:path";
4
+ import { afterEach, describe, expect, test } from "vitest";
5
+ import { ensureHarnessAdapters } from "../harness-adapters.js";
6
+ import { CANONICAL_SKILL_REL_PATH, detectHarnessContext } from "../harness-skill-targets.js";
7
+ import { generateWorkflowSkillContent } from "../workflow-skill-template.js";
8
+ import { VERSION } from "../../version.js";
9
+ const tempDirs = [];
10
+ afterEach(() => {
11
+ while (tempDirs.length > 0) {
12
+ const dir = tempDirs.pop();
13
+ if (dir) {
14
+ fs.rmSync(dir, { recursive: true, force: true });
15
+ }
16
+ }
17
+ });
18
+ describe("harness-skill-targets", () => {
19
+ test("无工具目录时不写适配层", () => {
20
+ const root = fs.mkdtempSync(path.join(os.tmpdir(), "harness-"));
21
+ tempDirs.push(root);
22
+ const detection = detectHarnessContext(root);
23
+ expect(detection.adaptersToWrite).toHaveLength(0);
24
+ expect(detection.detected).toEqual(["agents"]);
25
+ expect(detection.skillCanonical).toBe(CANONICAL_SKILL_REL_PATH);
26
+ });
27
+ test("存在 .trae 时自动写 trae Skill 镜像", () => {
28
+ const root = fs.mkdtempSync(path.join(os.tmpdir(), "harness-"));
29
+ tempDirs.push(root);
30
+ fs.mkdirSync(path.join(root, ".trae"), { recursive: true });
31
+ const skill = generateWorkflowSkillContent(VERSION);
32
+ const result = ensureHarnessAdapters(root, skill);
33
+ expect(result.adapters.some((a) => a.path === ".trae/skills/mcp-probe-kit/SKILL.md")).toBe(true);
34
+ const mirror = fs.readFileSync(path.join(root, ".trae/skills/mcp-probe-kit/SKILL.md"), "utf8");
35
+ expect(mirror).toBe(skill);
36
+ expect(result.detection.detected).toContain("trae");
37
+ });
38
+ test("存在 .lingma 时自动写规则指针", () => {
39
+ const root = fs.mkdtempSync(path.join(os.tmpdir(), "harness-"));
40
+ tempDirs.push(root);
41
+ fs.mkdirSync(path.join(root, ".lingma"), { recursive: true });
42
+ const result = ensureHarnessAdapters(root, generateWorkflowSkillContent(VERSION), "AGENTS.md");
43
+ const lingma = result.adapters.find((a) => a.id === "lingma-rules");
44
+ expect(lingma?.created).toBe(true);
45
+ const text = fs.readFileSync(path.join(root, ".lingma/rules/mcp-probe-kit-mcp.md"), "utf8");
46
+ expect(text).toContain("AGENTS.md");
47
+ expect(text).toContain(CANONICAL_SKILL_REL_PATH);
48
+ });
49
+ test("AGENTS.md 引用路径不因 harness 变化", () => {
50
+ const root = fs.mkdtempSync(path.join(os.tmpdir(), "harness-"));
51
+ tempDirs.push(root);
52
+ fs.mkdirSync(path.join(root, ".trae"), { recursive: true });
53
+ fs.mkdirSync(path.join(root, ".lingma"), { recursive: true });
54
+ ensureHarnessAdapters(root, generateWorkflowSkillContent(VERSION));
55
+ const detection = detectHarnessContext(root);
56
+ expect(detection.skillCanonical).toBe(".agents/skills/mcp-probe-kit/SKILL.md");
57
+ expect(detection.adaptersToWrite).toHaveLength(2);
58
+ });
59
+ });
@@ -43,7 +43,7 @@ describe("project-mcp-resources", () => {
43
43
  ensureAndDiscoverProjectResources(root);
44
44
  const content = readProjectResourceContent("probe://project/skill", root);
45
45
  expect(content?.mimeType).toBe("text/markdown");
46
- expect(content?.text).toContain("mcp-probe-kit-skill-version");
46
+ expect(content?.text).toContain("mcp-probe-kit-version:");
47
47
  });
48
48
  });
49
49
  });
@@ -0,0 +1,31 @@
1
+ import { describe, expect, test } from "vitest";
2
+ import { formatSkillFrontmatter, generateWorkflowSkillContent, MCP_PROBE_SKILL_NAME, } from "../workflow-skill-template.js";
3
+ import { formatSkillVersionMarker, parseSkillInstalledVersion, skillFrontmatterNeedsUpgrade, skillContentNeedsUpgrade, } from "../workflow-skill-version.js";
4
+ describe("workflow-skill frontmatter", () => {
5
+ test("生成的 Skill 以 YAML frontmatter 开头", () => {
6
+ const content = generateWorkflowSkillContent("3.6.6");
7
+ expect(content.startsWith("---\n")).toBe(true);
8
+ expect(content).toContain(`name: ${MCP_PROBE_SKILL_NAME}`);
9
+ expect(content).toContain("description: >-");
10
+ expect(content).toContain('mcp-probe-kit-version: "3.6.6"');
11
+ expect(content).toContain("# MCP 调用时机");
12
+ expect(content).not.toContain(formatSkillVersionMarker("3.6.6"));
13
+ });
14
+ test("从 frontmatter 解析版本", () => {
15
+ const header = formatSkillFrontmatter("3.6.5");
16
+ expect(parseSkillInstalledVersion(`${header}\n\n# body`)).toBe("3.6.5");
17
+ });
18
+ test("旧 HTML 注释版本仍可解析", () => {
19
+ const legacy = `${formatSkillVersionMarker("3.6.3")}\n# old\n`;
20
+ expect(parseSkillInstalledVersion(legacy)).toBe("3.6.3");
21
+ });
22
+ test("缺 frontmatter 时触发升级", () => {
23
+ const legacy = `${formatSkillVersionMarker("9.9.9")}\n# body\n`;
24
+ expect(skillFrontmatterNeedsUpgrade(legacy)).toBe(true);
25
+ expect(skillContentNeedsUpgrade(legacy, "3.6.6")).toBe(true);
26
+ });
27
+ test("同版本标准 frontmatter 不升级", () => {
28
+ const content = generateWorkflowSkillContent("3.6.6");
29
+ expect(skillContentNeedsUpgrade(content, "3.6.6")).toBe(false);
30
+ });
31
+ });
@@ -5,7 +5,7 @@ import { afterEach, describe, expect, test } from "vitest";
5
5
  import { VERSION } from "../../version.js";
6
6
  import { ensureAgentsMdSkillReference, ensureMcpProbeKitBootstrap, ensureMcpProbeSkill, resolveProjectRootFromToolArgs, } from "../workflow-skill-installer.js";
7
7
  import { formatSkillVersionMarker, agentsContextNeedsUpgrade, compareSemver, skillContentNeedsUpgrade, } from "../workflow-skill-version.js";
8
- import { LEGACY_WORKFLOW_SKILL_REL_PATH, MCP_PROBE_SKILL_REL_PATH, } from "../workflow-skill-template.js";
8
+ import { generateWorkflowSkillContent, LEGACY_WORKFLOW_SKILL_REL_PATH, MCP_PROBE_SKILL_REL_PATH, } from "../workflow-skill-template.js";
9
9
  import { wrapMcpProbeBlock } from "../merge-agents-md.js";
10
10
  const tempDirs = [];
11
11
  afterEach(() => {
@@ -36,10 +36,11 @@ describe("workflow-skill-installer", () => {
36
36
  expect(result.updated).toBe(false);
37
37
  expect(result.version).toBe(VERSION);
38
38
  const text = fs.readFileSync(path.join(root, MCP_PROBE_SKILL_REL_PATH), "utf8");
39
- expect(text).toContain(formatSkillVersionMarker(VERSION));
39
+ expect(text).toContain('name: mcp-probe-kit');
40
+ expect(text).toContain('mcp-probe-kit-version:');
40
41
  expect(text).toContain("MCP 调用时机");
41
42
  });
42
- test("同版本 Skill 不覆盖", () => {
43
+ test(" HTML 注释同版本会升级为 frontmatter", () => {
43
44
  const root = fs.mkdtempSync(path.join(os.tmpdir(), "wf-skill-"));
44
45
  tempDirs.push(root);
45
46
  const skillPath = path.join(root, MCP_PROBE_SKILL_REL_PATH);
@@ -47,11 +48,24 @@ describe("workflow-skill-installer", () => {
47
48
  fs.mkdirSync(path.dirname(skillPath), { recursive: true });
48
49
  fs.writeFileSync(skillPath, content, "utf8");
49
50
  const result = ensureMcpProbeSkill(root);
51
+ expect(result.updated).toBe(true);
52
+ const text = fs.readFileSync(skillPath, "utf8");
53
+ expect(text).toContain("name: mcp-probe-kit");
54
+ expect(text).not.toBe(content);
55
+ });
56
+ test("标准 frontmatter 同版本不覆盖", () => {
57
+ const root = fs.mkdtempSync(path.join(os.tmpdir(), "wf-skill-"));
58
+ tempDirs.push(root);
59
+ const skillPath = path.join(root, MCP_PROBE_SKILL_REL_PATH);
60
+ const content = generateWorkflowSkillContent(VERSION);
61
+ fs.mkdirSync(path.dirname(skillPath), { recursive: true });
62
+ fs.writeFileSync(skillPath, content, "utf8");
63
+ const result = ensureMcpProbeSkill(root);
50
64
  expect(result.created).toBe(false);
51
65
  expect(result.updated).toBe(false);
52
66
  expect(fs.readFileSync(skillPath, "utf8")).toBe(content);
53
67
  });
54
- test("旧版本 Skill 会升级覆盖", () => {
68
+ test("旧版 HTML 注释 Skill 会升级", () => {
55
69
  const root = fs.mkdtempSync(path.join(os.tmpdir(), "wf-skill-"));
56
70
  tempDirs.push(root);
57
71
  const skillPath = path.join(root, MCP_PROBE_SKILL_REL_PATH);
@@ -61,11 +75,11 @@ describe("workflow-skill-installer", () => {
61
75
  expect(result.updated).toBe(true);
62
76
  expect(result.previousVersion).toBe("0.1.0");
63
77
  const text = fs.readFileSync(skillPath, "utf8");
64
- expect(text).toContain(formatSkillVersionMarker(VERSION));
78
+ expect(text).toContain('name: mcp-probe-kit');
65
79
  expect(text).toContain("何时调用");
66
80
  expect(text).not.toContain("stale tools list");
67
81
  });
68
- test("无 AGENTS.md 时创建并引用 Skill", () => {
82
+ test("无 AGENTS.md 时创建并 @ 引用 Skill", () => {
69
83
  const root = fs.mkdtempSync(path.join(os.tmpdir(), "wf-skill-"));
70
84
  tempDirs.push(root);
71
85
  const result = ensureAgentsMdSkillReference(root);
@@ -73,6 +87,7 @@ describe("workflow-skill-installer", () => {
73
87
  const agents = fs.readFileSync(path.join(root, "AGENTS.md"), "utf8");
74
88
  expect(agents).toContain("mcp-probe:context-version");
75
89
  expect(agents).toContain(MCP_PROBE_SKILL_REL_PATH);
90
+ expect(agents).toContain(`@${MCP_PROBE_SKILL_REL_PATH}`);
76
91
  });
77
92
  test("已有 AGENTS.md 但缺 Skill 引用时更新", () => {
78
93
  const root = fs.mkdtempSync(path.join(os.tmpdir(), "wf-skill-"));
@@ -15,6 +15,7 @@ export interface AgentsMdTemplateInput {
15
15
  }>;
16
16
  projectRootPosix: string;
17
17
  graphReady: boolean;
18
+ contextReady?: boolean;
18
19
  /** @deprecated kept for callers; memory rules are always included in AGENTS.md */
19
20
  memoryEnabled?: boolean;
20
21
  }
@@ -1,4 +1,5 @@
1
1
  import { relativeLink } from "./project-context-layout.js";
2
+ import { formatAgentsSkillReference, formatAgentsSkillReferenceEn, } from "./agents-skill-ref.js";
2
3
  import { MCP_PROBE_SKILL_REL_PATH } from "./workflow-skill-template.js";
3
4
  function link(layout, targetRel) {
4
5
  return relativeLink(layout.indexPath, targetRel);
@@ -28,12 +29,20 @@ Memory (requires MEMORY_* env):
28
29
  */
29
30
  export function generateAgentsMdInner(input) {
30
31
  const { layout, locale } = input;
32
+ const contextReady = input.contextReady === true;
33
+ const graphReady = input.graphReady === true;
31
34
  const graph = link(layout, layout.latestMarkdownPath);
32
35
  const ctxIndex = link(layout, layout.legacyIndexPath);
33
36
  const skillLink = link(layout, MCP_PROBE_SKILL_REL_PATH);
37
+ const contextLineZh = contextReady
38
+ ? `上下文:写代码前先读 [project-context](${ctxIndex})(链到 \`${layout.modularDir}/\` 各文档)`
39
+ : `上下文:待 Agent 落盘后读 [project-context](${ctxIndex})(可先 \`init_project_context\` + 按模板写 \`${layout.modularDir}/\`)`;
40
+ const graphLineZh = graphReady
41
+ ? `图谱:大改前读 [latest](${graph});过期 \`code_insight\` mode=auto save_to_docs=true`
42
+ : `图谱:待 \`code_insight\` 落盘后读 [latest](${graph})(save_to_docs=true,由 Agent 写入)`;
34
43
  if (locale === "zh-CN") {
35
44
  return `## MCP(必须先调)
36
- 需已配置 mcp-probe-kit。写代码前先读 Skill:[MCP 调用时机](${skillLink})(\`${MCP_PROBE_SKILL_REL_PATH}\`,首次 MCP 调用自动创建)。
45
+ 需已配置 mcp-probe-kit。${formatAgentsSkillReference(skillLink)}(首次 MCP 调用自动创建 Skill 文件)。
37
46
 
38
47
  - 不确定用哪个 MCP → \`workflow\`(返回 firstTool)
39
48
  - 新功能 → \`start_feature\`(会先搜记忆)
@@ -43,11 +52,17 @@ export function generateAgentsMdInner(input) {
43
52
  - 缺上下文 → \`init_project_context\`
44
53
  - 提交 → \`gencommit\`
45
54
 
46
- 上下文:写代码前先读 [project-context](${ctxIndex})(链到 \`${layout.modularDir}/\` 各文档)
47
- 图谱:大改前读 [latest](${graph});过期 \`code_insight\` mode=auto save_to_docs=true${memorySection(locale)}`;
55
+ ${contextLineZh}
56
+ ${graphLineZh}${memorySection(locale)}`;
48
57
  }
58
+ const contextLineEn = contextReady
59
+ ? `Context: before coding read [project-context](${ctxIndex}) (links to \`${layout.modularDir}/\`)`
60
+ : `Context: read [project-context](${ctxIndex}) after Agent creates it (\`init_project_context\` + templates)`;
61
+ const graphLineEn = graphReady
62
+ ? `Graph: read [latest](${graph}) before large changes; refresh \`code_insight\` mode=auto save_to_docs=true`
63
+ : `Graph: read [latest](${graph}) after Agent saves code_insight output (save_to_docs=true)`;
49
64
  return `## MCP (call first)
50
- Requires mcp-probe-kit. Before coding, read Skill: [When to call MCP](${skillLink}) (\`${MCP_PROBE_SKILL_REL_PATH}\`, auto-created on first MCP call).
65
+ Requires mcp-probe-kit. ${formatAgentsSkillReferenceEn(skillLink)} (Skill file auto-created on first MCP call).
51
66
 
52
67
  - Unsure which MCP → \`workflow\` (returns firstTool)
53
68
  - Feature → \`start_feature\` (searches memory first)
@@ -57,8 +72,8 @@ Requires mcp-probe-kit. Before coding, read Skill: [When to call MCP](${skillLin
57
72
  - Missing context → \`init_project_context\`
58
73
  - Commit → \`gencommit\`
59
74
 
60
- Context: before coding read [project-context](${ctxIndex}) (links to \`${layout.modularDir}/\`)
61
- Graph: read [latest](${graph}) before large changes; refresh \`code_insight\` mode=auto save_to_docs=true${memorySection(locale)}`;
75
+ ${contextLineEn}
76
+ ${graphLineEn}${memorySection(locale)}`;
62
77
  }
63
78
  export function generateAgentsMdTemplate(input) {
64
79
  return generateAgentsMdInner(input);
@@ -0,0 +1,6 @@
1
+ export type AgentsSkillRefMode = "link" | "at" | "both";
2
+ /** AGENTS.md 中如何引用 mcp-probe-kit Skill(默认 both:@ 强绑定 + Markdown 链接) */
3
+ export declare function resolveAgentsSkillRefMode(): AgentsSkillRefMode;
4
+ export declare function formatAgentsSkillReference(skillLink: string, mode?: AgentsSkillRefMode): string;
5
+ export declare function formatAgentsSkillReferenceEn(skillLink: string, mode?: AgentsSkillRefMode): string;
6
+ export declare function agentsSkillReferenceSatisfied(content: string, mode?: AgentsSkillRefMode): boolean;
@@ -0,0 +1,45 @@
1
+ import { MCP_PROBE_SKILL_REL_PATH } from "./workflow-skill-template.js";
2
+ /** AGENTS.md 中如何引用 mcp-probe-kit Skill(默认 both:@ 强绑定 + Markdown 链接) */
3
+ export function resolveAgentsSkillRefMode() {
4
+ const raw = process.env.MCP_AGENTS_SKILL_REF?.trim().toLowerCase();
5
+ if (raw === "link" || raw === "at" || raw === "both") {
6
+ return raw;
7
+ }
8
+ return "both";
9
+ }
10
+ export function formatAgentsSkillReference(skillLink, mode = resolveAgentsSkillRefMode()) {
11
+ const atRef = `@${MCP_PROBE_SKILL_REL_PATH}`;
12
+ const linkRef = `[MCP 调用时机](${skillLink})`;
13
+ switch (mode) {
14
+ case "at":
15
+ return `写代码前先读 Skill:${atRef}`;
16
+ case "link":
17
+ return `写代码前先读 Skill:${linkRef}(\`${MCP_PROBE_SKILL_REL_PATH}\`)`;
18
+ default:
19
+ return `写代码前先读 Skill:${atRef}(或 ${linkRef})`;
20
+ }
21
+ }
22
+ export function formatAgentsSkillReferenceEn(skillLink, mode = resolveAgentsSkillRefMode()) {
23
+ const atRef = `@${MCP_PROBE_SKILL_REL_PATH}`;
24
+ const linkRef = `[When to call MCP](${skillLink})`;
25
+ switch (mode) {
26
+ case "at":
27
+ return `Before coding, read Skill: ${atRef}`;
28
+ case "link":
29
+ return `Before coding, read Skill: ${linkRef} (\`${MCP_PROBE_SKILL_REL_PATH}\`)`;
30
+ default:
31
+ return `Before coding, read Skill: ${atRef} (or ${linkRef})`;
32
+ }
33
+ }
34
+ export function agentsSkillReferenceSatisfied(content, mode = resolveAgentsSkillRefMode()) {
35
+ const hasAt = content.includes(`@${MCP_PROBE_SKILL_REL_PATH}`);
36
+ const hasLink = content.includes(MCP_PROBE_SKILL_REL_PATH);
37
+ switch (mode) {
38
+ case "at":
39
+ return hasAt;
40
+ case "link":
41
+ return hasLink;
42
+ default:
43
+ return hasAt && hasLink;
44
+ }
45
+ }
@@ -11,7 +11,18 @@ export interface FileDeliveryReport {
11
11
  writtenFiles: DeliveredFile[];
12
12
  pendingFiles: PendingFile[];
13
13
  }
14
+ export interface FileStatusEntry {
15
+ path: string;
16
+ exists: boolean;
17
+ written: boolean;
18
+ agent_action_required: boolean;
19
+ purpose?: string;
20
+ }
14
21
  export declare function toPosixRel(relPath: string): string;
15
22
  export declare function writeProjectFile(projectRoot: string, relPath: string, content: string, policy: "ifMissing" | "always"): DeliveredFile;
23
+ export declare function buildFileStatusEntries(projectRoot: string, entries: Array<{
24
+ path: string;
25
+ purpose?: string;
26
+ }>, writtenFiles: DeliveredFile[], pendingFiles: PendingFile[]): FileStatusEntry[];
16
27
  export declare function mergeDeliveryReports(...reports: FileDeliveryReport[]): FileDeliveryReport;
17
28
  export declare function formatFileDeliverySection(report: FileDeliveryReport): string;
@@ -14,6 +14,26 @@ export function writeProjectFile(projectRoot, relPath, content, policy) {
14
14
  fs.writeFileSync(absPath, content, "utf8");
15
15
  return { path: posixPath, action: existed ? "updated" : "created" };
16
16
  }
17
+ export function buildFileStatusEntries(projectRoot, entries, writtenFiles, pendingFiles) {
18
+ const writtenByMcp = new Set(writtenFiles
19
+ .filter((file) => file.action === "created" || file.action === "updated")
20
+ .map((file) => toPosixRel(file.path)));
21
+ const pendingPaths = new Set(pendingFiles.map((file) => toPosixRel(file.path)));
22
+ return entries.map(({ path: relPath, purpose }) => {
23
+ const posixPath = toPosixRel(relPath);
24
+ const absPath = path.join(path.resolve(projectRoot), ...posixPath.split("/"));
25
+ const exists = fs.existsSync(absPath);
26
+ const agent_action_required = pendingPaths.has(posixPath);
27
+ const written = writtenByMcp.has(posixPath) || (exists && !agent_action_required);
28
+ return {
29
+ path: posixPath,
30
+ ...(purpose !== undefined ? { purpose } : {}),
31
+ exists,
32
+ written,
33
+ agent_action_required,
34
+ };
35
+ });
36
+ }
17
37
  export function mergeDeliveryReports(...reports) {
18
38
  return {
19
39
  writtenFiles: reports.flatMap((report) => report.writtenFiles),
@@ -0,0 +1,19 @@
1
+ import { type HarnessAdapterKind, type HarnessDetectionResult, type LayoutHarnessManifest } from "./harness-skill-targets.js";
2
+ export interface HarnessAdapterWriteResult {
3
+ id: string;
4
+ path: string;
5
+ kind: HarnessAdapterKind;
6
+ created: boolean;
7
+ updated: boolean;
8
+ skipped: boolean;
9
+ }
10
+ export interface HarnessAdapterEnsureResult {
11
+ detection: HarnessDetectionResult;
12
+ adapters: HarnessAdapterWriteResult[];
13
+ layoutHarness: LayoutHarnessManifest;
14
+ }
15
+ /**
16
+ * Write optional harness adapters. AGENTS.md and canonical Skill are unchanged.
17
+ * Canonical Skill must already exist at `.agents/skills/mcp-probe-kit/SKILL.md`.
18
+ */
19
+ export declare function ensureHarnessAdapters(projectRoot: string, skillContent: string, agentsIndexPath?: string): HarnessAdapterEnsureResult;