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.
- 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__/file-delivery.unit.test.d.ts +1 -0
- package/build/lib/__tests__/file-delivery.unit.test.js +62 -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-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/file-delivery.d.ts +11 -0
- package/build/lib/file-delivery.js +20 -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/project-context-layout.d.ts +15 -2
- package/build/lib/project-context-layout.js +27 -3
- 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/index.d.ts +1 -1
- package/build/schemas/output/project-tools.d.ts +16 -1
- package/build/schemas/output/project-tools.js +7 -1
- package/build/schemas/project-tools.d.ts +1 -1
- package/build/schemas/project-tools.js +1 -1
- package/build/tools/__tests__/add_feature.write.unit.test.js +3 -0
- package/build/tools/__tests__/init_project.write.unit.test.js +3 -0
- package/build/tools/__tests__/init_project_context.unit.test.js +28 -0
- package/build/tools/add_feature.js +1 -1
- package/build/tools/init_project.js +8 -6
- package/build/tools/init_project_context.js +52 -30
- 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,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 @@
|
|
|
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
|
+
});
|
|
@@ -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
|
+
}
|
|
@@ -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;
|