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.
- package/README.md +3 -1
- package/build/index.js +1 -0
- package/build/lib/__tests__/agents-md-template.unit.test.js +2 -0
- package/build/lib/__tests__/agents-skill-ref.unit.test.d.ts +1 -0
- package/build/lib/__tests__/agents-skill-ref.unit.test.js +32 -0
- package/build/lib/__tests__/harness-adapters.unit.test.d.ts +1 -0
- package/build/lib/__tests__/harness-adapters.unit.test.js +68 -0
- package/build/lib/__tests__/harness-skill-targets.unit.test.d.ts +1 -0
- package/build/lib/__tests__/harness-skill-targets.unit.test.js +59 -0
- package/build/lib/__tests__/project-context-writer.unit.test.d.ts +1 -0
- package/build/lib/__tests__/project-context-writer.unit.test.js +28 -0
- package/build/lib/__tests__/project-mcp-resources.unit.test.js +1 -1
- package/build/lib/__tests__/workflow-skill-frontmatter.unit.test.d.ts +1 -0
- package/build/lib/__tests__/workflow-skill-frontmatter.unit.test.js +31 -0
- package/build/lib/__tests__/workflow-skill-installer.unit.test.js +21 -6
- package/build/lib/agents-md-template.d.ts +1 -0
- package/build/lib/agents-md-template.js +21 -6
- package/build/lib/agents-skill-ref.d.ts +6 -0
- package/build/lib/agents-skill-ref.js +45 -0
- package/build/lib/feature-spec-writer.d.ts +12 -0
- package/build/lib/feature-spec-writer.js +14 -0
- package/build/lib/file-delivery.d.ts +17 -0
- package/build/lib/file-delivery.js +42 -0
- package/build/lib/graph-insights-writer.d.ts +10 -0
- package/build/lib/graph-insights-writer.js +65 -0
- package/build/lib/harness-adapters.d.ts +19 -0
- package/build/lib/harness-adapters.js +166 -0
- package/build/lib/harness-skill-targets.d.ts +38 -0
- package/build/lib/harness-skill-targets.js +95 -0
- package/build/lib/init-project-writer.d.ts +7 -0
- package/build/lib/init-project-writer.js +161 -0
- package/build/lib/project-context-layout.d.ts +15 -2
- package/build/lib/project-context-layout.js +27 -3
- package/build/lib/project-context-writer.d.ts +41 -0
- package/build/lib/project-context-writer.js +120 -0
- package/build/lib/tool-annotations.js +2 -2
- package/build/lib/workflow-skill-installer.d.ts +2 -0
- package/build/lib/workflow-skill-installer.js +19 -5
- package/build/lib/workflow-skill-template.d.ts +3 -0
- package/build/lib/workflow-skill-template.js +12 -5
- package/build/lib/workflow-skill-version.d.ts +7 -1
- package/build/lib/workflow-skill-version.js +45 -2
- package/build/schemas/output/project-tools.d.ts +136 -0
- package/build/schemas/output/project-tools.js +65 -1
- package/build/tools/__tests__/add_feature.write.unit.test.d.ts +1 -0
- package/build/tools/__tests__/add_feature.write.unit.test.js +30 -0
- package/build/tools/__tests__/code_insight.unit.test.js +2 -4
- package/build/tools/__tests__/init_project.write.unit.test.d.ts +1 -0
- package/build/tools/__tests__/init_project.write.unit.test.js +22 -0
- package/build/tools/__tests__/init_project_context.unit.test.js +49 -45
- package/build/tools/add_feature.js +23 -6
- package/build/tools/code_insight.js +3 -3
- package/build/tools/init_project.js +25 -22
- package/build/tools/init_project_context.js +83 -46
- 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
|
|
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 @@
|
|
|
1
|
+
export {};
|
|
@@ -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,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 @@
|
|
|
1
|
+
export {};
|
|
@@ -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 @@
|
|
|
1
|
+
export {};
|
|
@@ -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-
|
|
46
|
+
expect(content?.text).toContain("mcp-probe-kit-version:");
|
|
47
47
|
});
|
|
48
48
|
});
|
|
49
49
|
});
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -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(
|
|
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("
|
|
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("
|
|
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(
|
|
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
|
|
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-"));
|
|
@@ -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
|
|
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
|
-
|
|
47
|
-
|
|
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.
|
|
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
|
-
|
|
61
|
-
|
|
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
|
+
}
|