@luckychen1993/pm-mcp 0.5.0 → 0.6.0
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 +57 -18
- package/dist/acceptance-tools.js +15 -10
- package/dist/audit-tools.js +28 -18
- package/dist/caller-workspace.js +244 -0
- package/dist/cli.js +11 -1
- package/dist/doctor.js +19 -16
- package/dist/git-state.js +41 -2
- package/dist/governance-audit.js +10 -0
- package/dist/governance-tools.js +54 -28
- package/dist/index-store.js +2 -1
- package/dist/index.js +12 -207
- package/dist/knowledge-tools.js +29 -20
- package/dist/paths.js +61 -3
- package/dist/project-tools.js +100 -22
- package/dist/prompts.js +91 -0
- package/dist/qoder-launch.js +131 -0
- package/dist/resources.js +116 -0
- package/dist/scan.js +4 -0
- package/dist/setup-project.js +1 -1
- package/dist/setup.js +49 -6
- package/dist/task-evidence.js +9 -9
- package/dist/task-tools.js +15 -10
- package/dist/tool-base.js +32 -7
- package/dist/typescript-semantic.js +28 -10
- package/dist/version.js +1 -1
- package/package.json +1 -1
package/dist/doctor.js
CHANGED
|
@@ -5,7 +5,7 @@ import { Client } from "@modelcontextprotocol/sdk/client/index.js";
|
|
|
5
5
|
import { StdioClientTransport } from "@modelcontextprotocol/sdk/client/stdio.js";
|
|
6
6
|
import { PACKAGE_SPEC, RUNTIME_CONTRACT, SERVER_NAME, TOOL_CATALOG_SIZE, VERSION } from "./version.js";
|
|
7
7
|
import { canonicalProjectRoot, findCodexProjectEntry, isLocalLaunch, isLocalServerEntry, isPackageLaunch, readCodexConfig } from "./setup-project.js";
|
|
8
|
-
import { localProjectLaunch, workbuddyConfigPath } from "./setup.js";
|
|
8
|
+
import { localProjectLaunch, qoderConfigPath, workbuddyConfigPath } from "./setup.js";
|
|
9
9
|
export function parseDoctorArgs(argv) {
|
|
10
10
|
const options = { root: process.cwd() };
|
|
11
11
|
for (let index = 0; index < argv.length; index++) {
|
|
@@ -19,8 +19,8 @@ export function parseDoctorArgs(argv) {
|
|
|
19
19
|
if (arg === "--config")
|
|
20
20
|
options.config = path.resolve(value);
|
|
21
21
|
if (arg === "--client") {
|
|
22
|
-
if (value !== "codex" && value !== "workbuddy")
|
|
23
|
-
throw new Error("doctor 支持 --client codex|workbuddy,其他客户端可用 --entry 验证本地入口。");
|
|
22
|
+
if (value !== "codex" && value !== "workbuddy" && value !== "qoder")
|
|
23
|
+
throw new Error("doctor 支持 --client codex|workbuddy|qoder,其他客户端可用 --entry 验证本地入口。");
|
|
24
24
|
options.client = value;
|
|
25
25
|
}
|
|
26
26
|
index++;
|
|
@@ -30,35 +30,35 @@ export function parseDoctorArgs(argv) {
|
|
|
30
30
|
return options;
|
|
31
31
|
}
|
|
32
32
|
/**
|
|
33
|
-
*
|
|
33
|
+
* 从标准 `{ "mcpServers": { ... } }` 配置文件(WorkBuddy 的 mcp.json、Qoder 的 settings.json)中取出 pm-mcp 条目。
|
|
34
34
|
*
|
|
35
35
|
* 只读取启动定义(command/args),不复制也不输出任何自定义 env / 凭据。
|
|
36
36
|
* 文件必须是**有界普通文件**:链接、目录、超 2MB 一律拒绝。
|
|
37
37
|
*/
|
|
38
|
-
function
|
|
38
|
+
function readJsonMcpEntry(file, label) {
|
|
39
39
|
if (!fs.existsSync(file))
|
|
40
40
|
return undefined;
|
|
41
41
|
const stat = fs.lstatSync(file);
|
|
42
42
|
if (!stat.isFile() || stat.isSymbolicLink() || stat.size > 2 * 1024 * 1024)
|
|
43
|
-
throw new Error(
|
|
43
|
+
throw new Error(`${label} 配置必须为有界普通文件,禁止链接。`);
|
|
44
44
|
let parsed;
|
|
45
45
|
try {
|
|
46
46
|
parsed = JSON.parse(fs.readFileSync(file, "utf8"));
|
|
47
47
|
}
|
|
48
48
|
catch (error) {
|
|
49
|
-
throw new Error(
|
|
49
|
+
throw new Error(`${label} 配置不是合法 JSON:${error.message}`);
|
|
50
50
|
}
|
|
51
51
|
const servers = parsed?.mcpServers;
|
|
52
52
|
const entry = servers?.[SERVER_NAME];
|
|
53
53
|
if (!entry)
|
|
54
54
|
return undefined;
|
|
55
55
|
if (entry.disabled === true)
|
|
56
|
-
throw new Error(
|
|
56
|
+
throw new Error(`${label} 中 pm-mcp 条目被禁用,现有客户端不会启动它。`);
|
|
57
57
|
if (typeof entry.command !== "string" || entry.command.length === 0)
|
|
58
|
-
throw new Error(
|
|
58
|
+
throw new Error(`${label} 中 pm-mcp 条目缺少可识别的 command。`);
|
|
59
59
|
const args = entry.args === undefined ? [] : entry.args;
|
|
60
60
|
if (!Array.isArray(args) || !args.every((value) => typeof value === "string"))
|
|
61
|
-
throw new Error(
|
|
61
|
+
throw new Error(`${label} 中 pm-mcp 条目的 args 不是字符串数组。`);
|
|
62
62
|
return { command: entry.command, args: args };
|
|
63
63
|
}
|
|
64
64
|
/** 短期本地读连接;不把探测新进程冒充已重新连接的客户端。 */
|
|
@@ -72,17 +72,20 @@ export async function runDoctor(options) {
|
|
|
72
72
|
try {
|
|
73
73
|
if (options.entry)
|
|
74
74
|
launch = { command: process.execPath, args: [options.entry, "--root", root] };
|
|
75
|
-
else if (options.client === "workbuddy") {
|
|
76
|
-
const
|
|
77
|
-
const
|
|
75
|
+
else if (options.client === "workbuddy" || options.client === "qoder") {
|
|
76
|
+
const label = options.client === "qoder" ? "Qoder" : "WorkBuddy";
|
|
77
|
+
const file = options.config ?? (options.client === "qoder"
|
|
78
|
+
? qoderConfigPath(process.env, os.homedir(), fs.existsSync)
|
|
79
|
+
: workbuddyConfigPath(process.env, os.homedir(), fs.existsSync));
|
|
80
|
+
const entry = readJsonMcpEntry(file, label);
|
|
78
81
|
if (!entry)
|
|
79
|
-
throw new Error(
|
|
82
|
+
throw new Error(`未在 ${label} 的 ${path.basename(file)} 中找到 pm-mcp 条目。`);
|
|
80
83
|
report.configured_server = SERVER_NAME;
|
|
81
84
|
if (!isLocalServerEntry(entry))
|
|
82
|
-
throw new Error(
|
|
85
|
+
throw new Error(`${label} 中的 pm-mcp 不是可识别的本地 pm-mcp 启动项;未执行未知启动命令。`);
|
|
83
86
|
// 配置通常不钉根(由会话工作区决定);探测需要明确目标根,故补上 --root。
|
|
84
87
|
launch = entry.args.includes("--root") ? entry : { command: entry.command, args: [...entry.args, "--root", root] };
|
|
85
|
-
report.configuration =
|
|
88
|
+
report.configuration = `${options.client}_local_probe`;
|
|
86
89
|
}
|
|
87
90
|
else {
|
|
88
91
|
if (!options.config && fs.existsSync(path.join(root, ".codex", "config.toml")))
|
package/dist/git-state.js
CHANGED
|
@@ -4,6 +4,7 @@ import { createHash } from "node:crypto";
|
|
|
4
4
|
import { spawnSync } from "node:child_process";
|
|
5
5
|
import { normSep } from "./budget.js";
|
|
6
6
|
import { hashRegularFile } from "./project-fingerprint.js";
|
|
7
|
+
import { ignoreMatcher, projectScanIgnores } from "./scan-policy.js";
|
|
7
8
|
export function projectFilePath(relative) {
|
|
8
9
|
const rel = path.posix.normalize(normSep(relative));
|
|
9
10
|
if (!relative || path.isAbsolute(rel) || /^[a-z]:/i.test(rel) || rel === "." || rel === ".." || rel.startsWith("../")) {
|
|
@@ -11,16 +12,54 @@ export function projectFilePath(relative) {
|
|
|
11
12
|
}
|
|
12
13
|
return rel;
|
|
13
14
|
}
|
|
15
|
+
let gitCommandCache;
|
|
16
|
+
/** PATH 缺 git 的宿主进程(对账 fail-closed 恒红)按常见安装路径兜底解析并缓存。 */
|
|
17
|
+
export function resolveGitCommand() {
|
|
18
|
+
if (gitCommandCache !== undefined)
|
|
19
|
+
return gitCommandCache;
|
|
20
|
+
const probe = (command) => spawnSync(command, ["--version"], { encoding: "utf8", timeout: 5000, windowsHide: true }).status === 0;
|
|
21
|
+
if (probe("git")) {
|
|
22
|
+
gitCommandCache = "git";
|
|
23
|
+
}
|
|
24
|
+
else if (process.platform === "win32") {
|
|
25
|
+
const where = spawnSync("where", ["git"], { encoding: "utf8", timeout: 5000, windowsHide: true });
|
|
26
|
+
const located = where.status === 0 ? where.stdout.split(/\r?\n/).find(Boolean)?.trim() : undefined;
|
|
27
|
+
const programFiles = process.env["ProgramFiles"] ?? "C:\\Program Files";
|
|
28
|
+
const candidates = [
|
|
29
|
+
...(located ? [located] : []),
|
|
30
|
+
path.join(programFiles, "Git", "cmd", "git.exe"),
|
|
31
|
+
"C:\\Program Files (x86)\\Git\\cmd\\git.exe",
|
|
32
|
+
path.join(process.env["LOCALAPPDATA"] ?? "", "Programs", "Git", "cmd", "git.exe"),
|
|
33
|
+
];
|
|
34
|
+
gitCommandCache = candidates.find((candidate) => candidate && fs.existsSync(candidate) && probe(candidate)) ?? null;
|
|
35
|
+
}
|
|
36
|
+
else {
|
|
37
|
+
gitCommandCache = ["/usr/bin/git", "/usr/local/bin/git", "/opt/homebrew/bin/git"].find((candidate) => probe(candidate)) ?? null;
|
|
38
|
+
}
|
|
39
|
+
return gitCommandCache;
|
|
40
|
+
}
|
|
41
|
+
/** 仅测试用:清空解析缓存,模拟不同 PATH 环境。 */
|
|
42
|
+
export function resetGitCommandCache() {
|
|
43
|
+
gitCommandCache = undefined;
|
|
44
|
+
}
|
|
14
45
|
/** 使用 NUL 分隔,避免中文转义、空格及文件名中的箭头被误解析。 */
|
|
15
46
|
export function changedGitFiles(root) {
|
|
16
|
-
const
|
|
47
|
+
const excluded = ignoreMatcher(projectScanIgnores(root));
|
|
48
|
+
const git = resolveGitCommand();
|
|
49
|
+
if (!git)
|
|
50
|
+
throw new Error("找不到 git 可执行文件(PATH 与常见安装路径均未命中),git 对账不可用");
|
|
51
|
+
const result = spawnSync(git, ["status", "--porcelain=v1", "-z", "-uall"], { cwd: root, encoding: "utf8", timeout: 10000, maxBuffer: 16 * 1024 * 1024 });
|
|
17
52
|
if (result.error || result.status !== 0)
|
|
18
53
|
throw new Error(result.error?.message ?? `git status 退出码 ${result.status}`);
|
|
19
54
|
const records = result.stdout.split("\0");
|
|
20
55
|
const changed = new Set();
|
|
21
56
|
const add = (file) => {
|
|
22
57
|
const rel = projectFilePath(file);
|
|
23
|
-
|
|
58
|
+
const parts = rel.split("/");
|
|
59
|
+
if (!rel.startsWith(".pm/") &&
|
|
60
|
+
rel !== "PROJECT.md" &&
|
|
61
|
+
rel !== ".gitignore" &&
|
|
62
|
+
!parts.some((_, index) => excluded(parts.slice(0, index + 1).join("/"), index < parts.length - 1)))
|
|
24
63
|
changed.add(rel);
|
|
25
64
|
};
|
|
26
65
|
for (let i = 0; i < records.length; i++) {
|
package/dist/governance-audit.js
CHANGED
|
@@ -101,6 +101,16 @@ function render(governance, graph, units, qualityCoverage, issues, maxLines) {
|
|
|
101
101
|
lines.push(`- 🚩 未读取/超限文件样例: ${graph.coverage.skippedFiles.slice(0, 8).join(", ")}`);
|
|
102
102
|
return foldLines(lines, { maxLines, hint: "用 impact_analysis 或治理过滤参数缩小范围" });
|
|
103
103
|
}
|
|
104
|
+
export function listGovernanceIssues(root, options = {}) {
|
|
105
|
+
const audit = auditGovernance(root, Number.MAX_SAFE_INTEGER);
|
|
106
|
+
const offset = Math.max(0, Math.trunc(options.offset ?? 0));
|
|
107
|
+
const limit = Math.min(100, Math.max(1, Math.trunc(options.limit ?? 50)));
|
|
108
|
+
const filtered = audit.issues.filter((issue) => (options.severity === undefined || options.severity === "all" || issue.severity === options.severity) &&
|
|
109
|
+
(!options.code || options.code === "all" || issue.code === options.code));
|
|
110
|
+
const items = filtered.slice(offset, offset + limit);
|
|
111
|
+
const nextOffset = offset + items.length < filtered.length ? offset + items.length : null;
|
|
112
|
+
return { total: filtered.length, offset, limit, items, nextOffset };
|
|
113
|
+
}
|
|
104
114
|
export function auditGovernance(root, maxLines = 150) {
|
|
105
115
|
const absolute = path.resolve(root);
|
|
106
116
|
const governance = loadGovernance(absolute);
|
package/dist/governance-tools.js
CHANGED
|
@@ -5,7 +5,7 @@ import { foldLines } from "./budget.js";
|
|
|
5
5
|
import { refreshDerived } from "./dashboard.js";
|
|
6
6
|
import { requireInitialized } from "./paths.js";
|
|
7
7
|
import { initGovernance, loadGovernance, saveGovernance, } from "./governance-model.js";
|
|
8
|
-
import { auditGovernance, dependencyGraphReport } from "./governance-audit.js";
|
|
8
|
+
import { auditGovernance, dependencyGraphReport, listGovernanceIssues } from "./governance-audit.js";
|
|
9
9
|
import { impactAnalysis } from "./semantic-graph.js";
|
|
10
10
|
import { assessQualityCoverage, createQualityPlan, discoverProjectUnits, runQualityPlan } from "./language-adapters.js";
|
|
11
11
|
import { buildPortfolioFromRegistry, buildPortfolioReport, loadPortfolioProject } from "./portfolio.js";
|
|
@@ -64,18 +64,21 @@ function qualityPlan(root, unit, kinds) {
|
|
|
64
64
|
function qualityLines(commands) {
|
|
65
65
|
return commands.map((command, index) => `${index + 1}. [${command.kind}] (${command.cwd}) ${command.command} ${command.args.join(" ")}`);
|
|
66
66
|
}
|
|
67
|
-
export function registerGovernanceTools(server,
|
|
68
|
-
toolI(server,
|
|
67
|
+
export function registerGovernanceTools(server, startupRoot) {
|
|
68
|
+
toolI(server, startupRoot, "init_governance", "为旧项目初始化 .pm/governance.json;新项目 init_project 已自动创建。", {}, (_args, ctx) => {
|
|
69
|
+
const root = ctx.root;
|
|
69
70
|
requireInitialized(root);
|
|
70
71
|
const governance = initGovernance(root, {});
|
|
71
72
|
refreshDerived(root);
|
|
72
73
|
return `✅ 治理模型已初始化(schema v${governance.schema_version})。下一步用 upsert_module 声明模块、owner 与语言。`;
|
|
73
74
|
});
|
|
74
|
-
toolR(server,
|
|
75
|
+
toolR(server, startupRoot, "get_governance", "查看结构化模块、owner、公开接口、仓库依赖与强制策略。", {}, (_args, ctx) => {
|
|
76
|
+
const root = ctx.root;
|
|
75
77
|
requireInitialized(root);
|
|
76
78
|
return compactGovernance(loadGovernance(root));
|
|
77
79
|
});
|
|
78
|
-
toolW(server,
|
|
80
|
+
toolW(server, startupRoot, "upsert_module", "新增或完整替换一个治理模块;root/owner/language/依赖边均经 schema 对账。", moduleInput, (args, ctx) => {
|
|
81
|
+
const root = ctx.root;
|
|
79
82
|
requireInitialized(root);
|
|
80
83
|
const governance = loadGovernance(root);
|
|
81
84
|
const value = {
|
|
@@ -90,14 +93,15 @@ export function registerGovernanceTools(server, root) {
|
|
|
90
93
|
refreshDerived(root);
|
|
91
94
|
return `✅ module ${value.id} 已保存(roots ${value.roots.length},owners ${value.owners.length},languages ${value.languages.length})。`;
|
|
92
95
|
});
|
|
93
|
-
toolW(server,
|
|
96
|
+
toolW(server, startupRoot, "upsert_interface", "新增或替换跨模块公开接口及契约文件、provider/consumers、版本。", {
|
|
94
97
|
id,
|
|
95
98
|
kind: z.string().trim().min(1),
|
|
96
99
|
provider: id,
|
|
97
100
|
consumers: texts,
|
|
98
101
|
contract_files: texts,
|
|
99
102
|
version: z.string().trim().min(1),
|
|
100
|
-
}, (args) => {
|
|
103
|
+
}, (args, ctx) => {
|
|
104
|
+
const root = ctx.root;
|
|
101
105
|
requireInitialized(root);
|
|
102
106
|
const governance = loadGovernance(root);
|
|
103
107
|
if (!governance.modules.some((module) => module.id === args.provider))
|
|
@@ -110,13 +114,14 @@ export function registerGovernanceTools(server, root) {
|
|
|
110
114
|
refreshDerived(root);
|
|
111
115
|
return `✅ interface ${args.id}@${args.version} 已保存:${args.provider} -> ${args.consumers.join(",") || "无消费者"}。`;
|
|
112
116
|
});
|
|
113
|
-
toolW(server,
|
|
117
|
+
toolW(server, startupRoot, "upsert_repository", "登记当前组合视图中的仓库、版本及跨仓 semver 约束。", {
|
|
114
118
|
id,
|
|
115
119
|
name: z.string().trim().min(1),
|
|
116
120
|
root: z.string().trim().min(1),
|
|
117
121
|
version: z.string().trim().min(1),
|
|
118
122
|
dependencies: z.array(z.object({ repository: id, constraint: z.string().trim().min(1) })),
|
|
119
|
-
}, (args) => {
|
|
123
|
+
}, (args, ctx) => {
|
|
124
|
+
const root = ctx.root;
|
|
120
125
|
requireInitialized(root);
|
|
121
126
|
const governance = loadGovernance(root);
|
|
122
127
|
replaceById(governance.repositories, args);
|
|
@@ -124,7 +129,7 @@ export function registerGovernanceTools(server, root) {
|
|
|
124
129
|
refreshDerived(root);
|
|
125
130
|
return `✅ repository ${args.id}@${args.version} 已保存(依赖 ${args.dependencies.length})。`;
|
|
126
131
|
});
|
|
127
|
-
toolW(server,
|
|
132
|
+
toolW(server, startupRoot, "set_governance_policies", "更新治理强制策略;未传字段保持原值。", {
|
|
128
133
|
enforce_ownership: z.boolean().optional(),
|
|
129
134
|
enforce_declared_dependencies: z.boolean().optional(),
|
|
130
135
|
fail_on_unresolved: z.boolean().optional(),
|
|
@@ -133,7 +138,8 @@ export function registerGovernanceTools(server, root) {
|
|
|
133
138
|
minimum_semantic_assurance: z.enum(["heuristic", "ast", "runtime"]).optional(),
|
|
134
139
|
fail_on_semantic_fallback: z.boolean().optional(),
|
|
135
140
|
required_quality_kinds: z.array(z.enum(["test", "build", "lint", "typecheck", "coverage", "security"])).optional(),
|
|
136
|
-
}, (args) => {
|
|
141
|
+
}, (args, ctx) => {
|
|
142
|
+
const root = ctx.root;
|
|
137
143
|
requireInitialized(root);
|
|
138
144
|
const governance = loadGovernance(root);
|
|
139
145
|
governance.policies = { ...governance.policies, ...args };
|
|
@@ -141,7 +147,8 @@ export function registerGovernanceTools(server, root) {
|
|
|
141
147
|
refreshDerived(root);
|
|
142
148
|
return `✅ 治理策略已更新:coverage>=${governance.policies.minimum_coverage_pct}% · semantic>=${governance.policies.minimum_semantic_assurance} · regex-fallback=${governance.policies.fail_on_semantic_fallback ? "forbidden" : "allowed"}`;
|
|
143
149
|
});
|
|
144
|
-
toolR(server,
|
|
150
|
+
toolR(server, startupRoot, "discover_languages", "递归发现 Node/Python/Go/Rust/JVM/.NET 单元及其受控质量命令,不执行命令。", {}, (_args, ctx) => {
|
|
151
|
+
const root = ctx.root;
|
|
145
152
|
requireInitialized(root);
|
|
146
153
|
const units = discoverProjectUnits(root);
|
|
147
154
|
const coverage = assessQualityCoverage(units);
|
|
@@ -152,41 +159,57 @@ export function registerGovernanceTools(server, root) {
|
|
|
152
159
|
lines.push(`- 🚩 无质量命令: ${coverage.withoutCommands.join(", ")}`);
|
|
153
160
|
return foldLines(lines, { maxLines: budgetLines(root), hint: "按模块拆分治理 root" });
|
|
154
161
|
});
|
|
155
|
-
toolR(server,
|
|
162
|
+
toolR(server, startupRoot, "audit_governance", "审计跨文件/模块/语言语义覆盖、owner、公开接口、依赖边界、循环、unresolved 与质量矩阵覆盖。", {}, (_args, ctx) => {
|
|
163
|
+
const root = ctx.root;
|
|
156
164
|
requireInitialized(root);
|
|
157
165
|
return auditGovernance(root, budgetLines(root)).report;
|
|
158
166
|
});
|
|
159
|
-
toolR(server,
|
|
167
|
+
toolR(server, startupRoot, "list_governance_issues", "分页读取完整治理审计问题,不受 audit_governance 文本预算折叠影响;只读且按 offset/limit 返回。", {
|
|
168
|
+
offset: z.number().int().min(0).optional().describe("起始偏移,默认 0"),
|
|
169
|
+
limit: z.number().int().min(1).max(100).optional().describe("每页数量,默认 50"),
|
|
170
|
+
severity: z.enum(["all", "error", "warning"]).optional().describe("按级别过滤"),
|
|
171
|
+
code: z.string().trim().max(100).optional().describe("按问题 code 过滤"),
|
|
172
|
+
}, (args, ctx) => {
|
|
173
|
+
const root = ctx.root;
|
|
174
|
+
requireInitialized(root);
|
|
175
|
+
return JSON.stringify(listGovernanceIssues(root, args ?? {}), null, 2);
|
|
176
|
+
});
|
|
177
|
+
toolR(server, startupRoot, "dependency_graph", "查询跨文件/模块依赖图:全局摘要(关系/环/入出边枢纽)、模块与文件级循环、聚焦文件的依赖邻域(BFS 方向与深度可调、未解析引用)。", {
|
|
160
178
|
focus: z.string().trim().min(1).max(512).optional().describe("聚焦文件(相对项目根路径)"),
|
|
161
179
|
direction: z.enum(["deps", "dependents", "both"]).optional().describe("邻域方向,默认 both"),
|
|
162
180
|
depth: z.number().int().min(1).max(6).optional().describe("邻域深度 1-6,默认 2"),
|
|
163
|
-
}, (args) => {
|
|
181
|
+
}, (args, ctx) => {
|
|
182
|
+
const root = ctx.root;
|
|
164
183
|
requireInitialized(root);
|
|
165
184
|
return dependencyGraphReport(root, args ?? {}, budgetLines(root));
|
|
166
185
|
});
|
|
167
|
-
toolR(server,
|
|
186
|
+
toolR(server, startupRoot, "list_semantic_evidence", "列出已登记的语言原生 AST、编译器或运行时语义证据;损坏文档会使整次读取失败。", {}, (_args, ctx) => {
|
|
187
|
+
const root = ctx.root;
|
|
168
188
|
requireInitialized(root);
|
|
169
189
|
return JSON.stringify(listSemanticEvidence(root), null, 2);
|
|
170
190
|
});
|
|
171
|
-
toolI(server,
|
|
191
|
+
toolI(server, startupRoot, "replace_semantic_evidence_for_file", "按源码文件原子替换语义证据:删除该文件旧 evidence,仅保留当前 hash-bound evidence。", {
|
|
172
192
|
id: z.string().trim().min(1).max(128),
|
|
173
193
|
document: SemanticEvidenceDocumentSchema,
|
|
174
|
-
}, (args) => {
|
|
194
|
+
}, (args, ctx) => {
|
|
195
|
+
const root = ctx.root;
|
|
175
196
|
requireInitialized(root);
|
|
176
197
|
const saved = replaceSemanticEvidenceForFile(root, args.id, args.document);
|
|
177
198
|
return `✅ semantic evidence replaced ${args.id}:${saved.file} · ${saved.analyzer.id} · ${saved.analyzer.assurance} · ${saved.status}`;
|
|
178
199
|
});
|
|
179
|
-
toolI(server,
|
|
200
|
+
toolI(server, startupRoot, "save_semantic_evidence", "保存一份绑定源码 SHA-256 的语言原生 AST/编译器/运行时证据;过期摘要在治理审计中严格失败。", {
|
|
180
201
|
id: z.string().trim().min(1).max(128),
|
|
181
202
|
document: SemanticEvidenceDocumentSchema,
|
|
182
|
-
}, (args) => {
|
|
203
|
+
}, (args, ctx) => {
|
|
204
|
+
const root = ctx.root;
|
|
183
205
|
requireInitialized(root);
|
|
184
206
|
const saved = saveSemanticEvidence(root, args.id, args.document);
|
|
185
207
|
return `✅ semantic evidence ${args.id} 已保存:${saved.file} · ${saved.analyzer.id} · ${saved.analyzer.assurance} · ${saved.status}`;
|
|
186
208
|
});
|
|
187
|
-
toolR(server,
|
|
209
|
+
toolR(server, startupRoot, "impact_analysis", "从变更文件沿 file/module 反向依赖闭包计算受影响文件与模块。", {
|
|
188
210
|
files: z.array(z.string().min(1)).min(1).max(100),
|
|
189
|
-
}, (args) => {
|
|
211
|
+
}, (args, ctx) => {
|
|
212
|
+
const root = ctx.root;
|
|
190
213
|
requireInitialized(root);
|
|
191
214
|
const audit = auditGovernance(root, budgetLines(root));
|
|
192
215
|
const impact = impactAnalysis(audit.graph, args.files);
|
|
@@ -197,10 +220,11 @@ export function registerGovernanceTools(server, root) {
|
|
|
197
220
|
...(impact.unknownChangedFiles.length ? [`⚠️ 未进入语义图: ${impact.unknownChangedFiles.join(", ")}`] : []),
|
|
198
221
|
], { maxLines: budgetLines(root), hint: "缩小 files 输入或按模块运行" });
|
|
199
222
|
});
|
|
200
|
-
toolR(server,
|
|
223
|
+
toolR(server, startupRoot, "plan_quality_matrix", "生成跨语言真实质量命令计划;只规划,不启动进程。", {
|
|
201
224
|
unit: z.string().optional(),
|
|
202
225
|
kinds: z.array(z.enum(["test", "build", "lint", "typecheck", "coverage", "security"])).optional(),
|
|
203
|
-
}, (args) => {
|
|
226
|
+
}, (args, ctx) => {
|
|
227
|
+
const root = ctx.root;
|
|
204
228
|
requireInitialized(root);
|
|
205
229
|
const plan = qualityPlan(root, args.unit, args.kinds);
|
|
206
230
|
return foldLines([
|
|
@@ -209,12 +233,13 @@ export function registerGovernanceTools(server, root) {
|
|
|
209
233
|
...(plan.coverage.withoutCommands.length ? [`🚩 无命令单元: ${plan.coverage.withoutCommands.join(", ")}`] : []),
|
|
210
234
|
], { maxLines: budgetLines(root), hint: "用 unit/kinds 过滤;真实执行需 run_quality_matrix confirm_execute=true" });
|
|
211
235
|
});
|
|
212
|
-
toolI(server,
|
|
236
|
+
toolI(server, startupRoot, "run_quality_matrix", "显式执行跨语言质量矩阵。只运行受支持适配器生成的 argv,shell=false;缺工具/超时/非零均失败。", {
|
|
213
237
|
confirm_execute: z.literal(true).describe("必须显式为 true;该操作会运行目标项目构建/测试命令"),
|
|
214
238
|
unit: z.string().optional(),
|
|
215
239
|
kinds: z.array(z.enum(["test", "build", "lint", "typecheck", "coverage", "security"])).optional(),
|
|
216
240
|
stop_on_failure: z.boolean().optional(),
|
|
217
|
-
}, async (args) => {
|
|
241
|
+
}, async (args, ctx) => {
|
|
242
|
+
const root = ctx.root;
|
|
218
243
|
requireInitialized(root);
|
|
219
244
|
if (args.confirm_execute !== true)
|
|
220
245
|
throw new Error("必须显式 confirm_execute=true");
|
|
@@ -237,9 +262,10 @@ export function registerGovernanceTools(server, root) {
|
|
|
237
262
|
lines.push("命令执行结果与任务完成证据分开判断;未知计数不等于已观察测试通过。");
|
|
238
263
|
return foldLines(lines, { maxLines: budgetLines(root), hint: "查看 .pm/quality-runs 结构化摘要(不落原始输出)" });
|
|
239
264
|
});
|
|
240
|
-
toolR(server,
|
|
265
|
+
toolR(server, startupRoot, "get_portfolio", "聚合当前项目或全局注册项目的阶段、里程碑、债务、安全、语言、跨仓依赖/版本/cycle;加载失败不能假绿。", {
|
|
241
266
|
current_only: z.boolean().optional(),
|
|
242
|
-
}, (args) => {
|
|
267
|
+
}, (args, ctx) => {
|
|
268
|
+
const root = ctx.root;
|
|
243
269
|
requireInitialized(root);
|
|
244
270
|
const portfolio = args.current_only
|
|
245
271
|
? buildPortfolioReport({ projects: [loadPortfolioProject(root)], projectsRequested: 1 })
|
package/dist/index-store.js
CHANGED
|
@@ -35,7 +35,8 @@ export function computeEntry(root, rel, stat) {
|
|
|
35
35
|
return {
|
|
36
36
|
loc: countLoc(content),
|
|
37
37
|
skip: needsSkipCount(rel, isTest) ? countSkipLines(content) : 0,
|
|
38
|
-
|
|
38
|
+
// fixtures 是测试的输入而非测试本身,不参与空测试嫌疑
|
|
39
|
+
trivial: isTest && !/(^|\/)fixtures(\/|$)/.test(rel) && looksTrivialTest(content),
|
|
39
40
|
oversize: false,
|
|
40
41
|
contentOk: true,
|
|
41
42
|
};
|