mcp-probe-kit 3.6.3 → 3.6.8

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 (55) 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__/harness-adapters.unit.test.d.ts +1 -0
  7. package/build/lib/__tests__/harness-adapters.unit.test.js +68 -0
  8. package/build/lib/__tests__/harness-skill-targets.unit.test.d.ts +1 -0
  9. package/build/lib/__tests__/harness-skill-targets.unit.test.js +59 -0
  10. package/build/lib/__tests__/project-context-writer.unit.test.d.ts +1 -0
  11. package/build/lib/__tests__/project-context-writer.unit.test.js +28 -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/feature-spec-writer.d.ts +12 -0
  21. package/build/lib/feature-spec-writer.js +14 -0
  22. package/build/lib/file-delivery.d.ts +17 -0
  23. package/build/lib/file-delivery.js +42 -0
  24. package/build/lib/graph-insights-writer.d.ts +10 -0
  25. package/build/lib/graph-insights-writer.js +65 -0
  26. package/build/lib/harness-adapters.d.ts +19 -0
  27. package/build/lib/harness-adapters.js +166 -0
  28. package/build/lib/harness-skill-targets.d.ts +38 -0
  29. package/build/lib/harness-skill-targets.js +95 -0
  30. package/build/lib/init-project-writer.d.ts +7 -0
  31. package/build/lib/init-project-writer.js +161 -0
  32. package/build/lib/project-context-layout.d.ts +15 -2
  33. package/build/lib/project-context-layout.js +27 -3
  34. package/build/lib/project-context-writer.d.ts +41 -0
  35. package/build/lib/project-context-writer.js +120 -0
  36. package/build/lib/tool-annotations.js +2 -2
  37. package/build/lib/workflow-skill-installer.d.ts +2 -0
  38. package/build/lib/workflow-skill-installer.js +19 -5
  39. package/build/lib/workflow-skill-template.d.ts +3 -0
  40. package/build/lib/workflow-skill-template.js +12 -5
  41. package/build/lib/workflow-skill-version.d.ts +7 -1
  42. package/build/lib/workflow-skill-version.js +45 -2
  43. package/build/schemas/output/project-tools.d.ts +136 -0
  44. package/build/schemas/output/project-tools.js +65 -1
  45. package/build/tools/__tests__/add_feature.write.unit.test.d.ts +1 -0
  46. package/build/tools/__tests__/add_feature.write.unit.test.js +30 -0
  47. package/build/tools/__tests__/code_insight.unit.test.js +2 -4
  48. package/build/tools/__tests__/init_project.write.unit.test.d.ts +1 -0
  49. package/build/tools/__tests__/init_project.write.unit.test.js +22 -0
  50. package/build/tools/__tests__/init_project_context.unit.test.js +49 -45
  51. package/build/tools/add_feature.js +23 -6
  52. package/build/tools/code_insight.js +3 -3
  53. package/build/tools/init_project.js +25 -22
  54. package/build/tools/init_project_context.js +83 -46
  55. 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,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
+ });
@@ -0,0 +1,28 @@
1
+ import fs from "node:fs";
2
+ import os from "node:os";
3
+ import path from "node:path";
4
+ import { describe, expect, test } from "vitest";
5
+ import { resolveProjectContextLayout } from "../project-context-layout.js";
6
+ import { writeProjectContextArtifacts } from "../project-context-writer.js";
7
+ describe("project-context-writer", () => {
8
+ test("写入 AGENTS.md 与 modular 骨架", () => {
9
+ const projectRoot = fs.mkdtempSync(path.join(os.tmpdir(), "mcp-probe-writer-"));
10
+ const layout = resolveProjectContextLayout(projectRoot, { docs_dir: "docs" });
11
+ const docs = [
12
+ { file: "tech-stack.md", title: "技术栈", purpose: "技术栈说明" },
13
+ { file: "architecture.md", title: "架构设计", purpose: "架构说明" },
14
+ ];
15
+ const result = writeProjectContextArtifacts({
16
+ layout,
17
+ mergedAgentsContent: "# AGENTS\n",
18
+ skipModularDocs: false,
19
+ projectInfo: { name: "demo", version: "1.0.0", description: "test" },
20
+ detection: { language: "TypeScript", framework: "node", category: "library" },
21
+ docs,
22
+ });
23
+ expect(result.agentsMd.action).toBe("created");
24
+ expect(fs.existsSync(path.join(projectRoot, "AGENTS.md"))).toBe(true);
25
+ expect(fs.existsSync(path.join(projectRoot, "docs", "project-context.md"))).toBe(true);
26
+ expect(fs.existsSync(path.join(projectRoot, "docs", "project-context", "tech-stack.md"))).toBe(true);
27
+ });
28
+ });
@@ -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
+ }
@@ -0,0 +1,12 @@
1
+ import { type FileDeliveryReport } from "./file-delivery.js";
2
+ export interface FeatureSpecTemplates {
3
+ requirements: string;
4
+ design: string;
5
+ tasks: string;
6
+ }
7
+ export declare function writeFeatureSpecFiles(input: {
8
+ projectRoot: string;
9
+ docsDir: string;
10
+ featureName: string;
11
+ templates: FeatureSpecTemplates;
12
+ }): FileDeliveryReport;
@@ -0,0 +1,14 @@
1
+ import { mergeDeliveryReports, writeProjectFile, } from "./file-delivery.js";
2
+ export function writeFeatureSpecFiles(input) {
3
+ const base = `${input.docsDir}/specs/${input.featureName}`;
4
+ const root = input.projectRoot;
5
+ const report = mergeDeliveryReports({
6
+ writtenFiles: [
7
+ writeProjectFile(root, `${base}/requirements.md`, input.templates.requirements, "ifMissing"),
8
+ writeProjectFile(root, `${base}/design.md`, input.templates.design, "ifMissing"),
9
+ writeProjectFile(root, `${base}/tasks.md`, input.templates.tasks, "ifMissing"),
10
+ ],
11
+ pendingFiles: [],
12
+ });
13
+ return report;
14
+ }
@@ -0,0 +1,17 @@
1
+ export type FileWriteAction = "created" | "updated" | "skipped";
2
+ export interface DeliveredFile {
3
+ path: string;
4
+ action: FileWriteAction;
5
+ }
6
+ export interface PendingFile {
7
+ path: string;
8
+ reason: string;
9
+ }
10
+ export interface FileDeliveryReport {
11
+ writtenFiles: DeliveredFile[];
12
+ pendingFiles: PendingFile[];
13
+ }
14
+ export declare function toPosixRel(relPath: string): string;
15
+ export declare function writeProjectFile(projectRoot: string, relPath: string, content: string, policy: "ifMissing" | "always"): DeliveredFile;
16
+ export declare function mergeDeliveryReports(...reports: FileDeliveryReport[]): FileDeliveryReport;
17
+ export declare function formatFileDeliverySection(report: FileDeliveryReport): string;
@@ -0,0 +1,42 @@
1
+ import * as fs from "node:fs";
2
+ import * as path from "node:path";
3
+ export function toPosixRel(relPath) {
4
+ return relPath.replace(/\\/g, "/");
5
+ }
6
+ export function writeProjectFile(projectRoot, relPath, content, policy) {
7
+ const posixPath = toPosixRel(relPath);
8
+ const absPath = path.join(path.resolve(projectRoot), ...posixPath.split("/"));
9
+ const existed = fs.existsSync(absPath);
10
+ if (policy === "ifMissing" && existed) {
11
+ return { path: posixPath, action: "skipped" };
12
+ }
13
+ fs.mkdirSync(path.dirname(absPath), { recursive: true });
14
+ fs.writeFileSync(absPath, content, "utf8");
15
+ return { path: posixPath, action: existed ? "updated" : "created" };
16
+ }
17
+ export function mergeDeliveryReports(...reports) {
18
+ return {
19
+ writtenFiles: reports.flatMap((report) => report.writtenFiles),
20
+ pendingFiles: reports.flatMap((report) => report.pendingFiles),
21
+ };
22
+ }
23
+ export function formatFileDeliverySection(report) {
24
+ const lines = ["## 文件落盘状态"];
25
+ if (report.writtenFiles.length > 0) {
26
+ lines.push("", "### 已写入(MCP 服务端)");
27
+ for (const file of report.writtenFiles) {
28
+ const verb = file.action === "created" ? "已创建" : file.action === "updated" ? "已更新" : "已跳过(已存在)";
29
+ lines.push(`- \`${file.path}\` — ${verb}`);
30
+ }
31
+ }
32
+ if (report.pendingFiles.length > 0) {
33
+ lines.push("", "### 尚未创建(仅规划 / 待后续步骤)");
34
+ for (const file of report.pendingFiles) {
35
+ lines.push(`- \`${file.path}\` — ${file.reason}`);
36
+ }
37
+ }
38
+ if (report.writtenFiles.length === 0 && report.pendingFiles.length === 0) {
39
+ lines.push("", "- 无文件变更");
40
+ }
41
+ return lines.join("\n");
42
+ }
@@ -0,0 +1,10 @@
1
+ import type { CodeInsightBridgeResult } from "./gitnexus-bridge.js";
2
+ import { type ProjectContextLayout } from "./project-context-layout.js";
3
+ import { type FileDeliveryReport } from "./file-delivery.js";
4
+ export declare function buildGraphInsightMarkdown(result: CodeInsightBridgeResult, structured: Record<string, unknown>): string;
5
+ export declare function persistGraphInsightsDocs(input: {
6
+ projectRoot: string;
7
+ layout: ProjectContextLayout;
8
+ result: CodeInsightBridgeResult;
9
+ structured: Record<string, unknown>;
10
+ }): FileDeliveryReport;
@@ -0,0 +1,65 @@
1
+ import * as fs from "node:fs";
2
+ import * as path from "node:path";
3
+ import { layoutAbsPath } from "./project-context-layout.js";
4
+ import { mergeDeliveryReports, toPosixRel, writeProjectFile, } from "./file-delivery.js";
5
+ function makeSafeSegment(value) {
6
+ return (value || "code-insight")
7
+ .toLowerCase()
8
+ .replace(/[^a-z0-9._-]+/g, "-")
9
+ .replace(/-+/g, "-")
10
+ .replace(/^-|-$/g, "")
11
+ .slice(0, 48) || "code-insight";
12
+ }
13
+ export function buildGraphInsightMarkdown(result, structured) {
14
+ const mode = structured.mode;
15
+ const executionLines = result.executions
16
+ .map((item) => {
17
+ if (item.ok) {
18
+ return `- **${item.tool}**${item.status ? ` [${item.status}]` : ""}: ${(item.text || "已返回结果").replace(/\s+/g, " ").slice(0, 500)}`;
19
+ }
20
+ return `- **${item.tool}** ⚠️: ${(item.error || "调用失败").replace(/\s+/g, " ").slice(0, 300)}`;
21
+ })
22
+ .join("\n");
23
+ return `# 代码图谱洞察
24
+
25
+ > 由 mcp-probe-kit \`code_insight\` 自动生成
26
+
27
+ | 字段 | 值 |
28
+ |------|-----|
29
+ | 状态 | ${String(structured.status ?? "ok")} |
30
+ | 模式 | ${mode?.requested ?? "auto"} → ${mode?.resolved ?? "auto"} |
31
+ | 来源 | ${result.provider} |
32
+ | 工作区 | ${result.workspaceMode} |
33
+ | 生成时间 | ${new Date().toISOString()} |
34
+
35
+ ## 摘要
36
+
37
+ ${result.summary}
38
+
39
+ ## 执行记录
40
+
41
+ ${executionLines || "- 无执行记录"}
42
+
43
+ ${result.warnings.length > 0 ? `\n## 警告\n\n${result.warnings.map((w) => `- ${w}`).join("\n")}\n` : ""}`;
44
+ }
45
+ export function persistGraphInsightsDocs(input) {
46
+ const markdown = buildGraphInsightMarkdown(input.result, input.structured);
47
+ const json = JSON.stringify(input.structured, null, 2);
48
+ const root = path.resolve(input.projectRoot);
49
+ const latestMd = writeProjectFile(root, input.layout.latestMarkdownPath, markdown, "always");
50
+ const latestJson = writeProjectFile(root, input.layout.latestJsonPath, json, "always");
51
+ const graphAbs = layoutAbsPath(input.layout, input.layout.graphDir);
52
+ fs.mkdirSync(graphAbs, { recursive: true });
53
+ const timestamp = new Date().toISOString().replace(/[:.]/g, "-");
54
+ const suffix = makeSafeSegment(input.result.summary || String(input.structured.status ?? "ok"));
55
+ const mode = input.structured.mode?.resolved ?? "auto";
56
+ const archiveBase = `${timestamp}-${mode}-${suffix}`;
57
+ const archiveMdRel = toPosixRel(path.join(input.layout.graphDir, `${archiveBase}.md`));
58
+ const archiveJsonRel = toPosixRel(path.join(input.layout.graphDir, `${archiveBase}.json`));
59
+ const archiveMd = writeProjectFile(root, archiveMdRel, markdown, "always");
60
+ const archiveJson = writeProjectFile(root, archiveJsonRel, json, "always");
61
+ return mergeDeliveryReports({
62
+ writtenFiles: [latestMd, latestJson, archiveMd, archiveJson],
63
+ pendingFiles: [],
64
+ });
65
+ }